Trajectree 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. trajectree/__init__.py +0 -3
  2. trajectree/fock_optics/devices.py +1 -1
  3. trajectree/fock_optics/light_sources.py +2 -2
  4. trajectree/fock_optics/measurement.py +3 -3
  5. trajectree/fock_optics/utils.py +6 -6
  6. trajectree/trajectory.py +2 -2
  7. {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/METADATA +2 -3
  8. trajectree-0.0.2.dist-info/RECORD +16 -0
  9. trajectree/quimb/docs/_pygments/_pygments_dark.py +0 -118
  10. trajectree/quimb/docs/_pygments/_pygments_light.py +0 -118
  11. trajectree/quimb/docs/conf.py +0 -158
  12. trajectree/quimb/docs/examples/ex_mpi_expm_evo.py +0 -62
  13. trajectree/quimb/quimb/__init__.py +0 -507
  14. trajectree/quimb/quimb/calc.py +0 -1491
  15. trajectree/quimb/quimb/core.py +0 -2279
  16. trajectree/quimb/quimb/evo.py +0 -712
  17. trajectree/quimb/quimb/experimental/__init__.py +0 -0
  18. trajectree/quimb/quimb/experimental/autojittn.py +0 -129
  19. trajectree/quimb/quimb/experimental/belief_propagation/__init__.py +0 -109
  20. trajectree/quimb/quimb/experimental/belief_propagation/bp_common.py +0 -397
  21. trajectree/quimb/quimb/experimental/belief_propagation/d1bp.py +0 -316
  22. trajectree/quimb/quimb/experimental/belief_propagation/d2bp.py +0 -653
  23. trajectree/quimb/quimb/experimental/belief_propagation/hd1bp.py +0 -571
  24. trajectree/quimb/quimb/experimental/belief_propagation/hv1bp.py +0 -775
  25. trajectree/quimb/quimb/experimental/belief_propagation/l1bp.py +0 -316
  26. trajectree/quimb/quimb/experimental/belief_propagation/l2bp.py +0 -537
  27. trajectree/quimb/quimb/experimental/belief_propagation/regions.py +0 -194
  28. trajectree/quimb/quimb/experimental/cluster_update.py +0 -286
  29. trajectree/quimb/quimb/experimental/merabuilder.py +0 -865
  30. trajectree/quimb/quimb/experimental/operatorbuilder/__init__.py +0 -15
  31. trajectree/quimb/quimb/experimental/operatorbuilder/operatorbuilder.py +0 -1631
  32. trajectree/quimb/quimb/experimental/schematic.py +0 -7
  33. trajectree/quimb/quimb/experimental/tn_marginals.py +0 -130
  34. trajectree/quimb/quimb/experimental/tnvmc.py +0 -1483
  35. trajectree/quimb/quimb/gates.py +0 -36
  36. trajectree/quimb/quimb/gen/__init__.py +0 -2
  37. trajectree/quimb/quimb/gen/operators.py +0 -1167
  38. trajectree/quimb/quimb/gen/rand.py +0 -713
  39. trajectree/quimb/quimb/gen/states.py +0 -479
  40. trajectree/quimb/quimb/linalg/__init__.py +0 -6
  41. trajectree/quimb/quimb/linalg/approx_spectral.py +0 -1109
  42. trajectree/quimb/quimb/linalg/autoblock.py +0 -258
  43. trajectree/quimb/quimb/linalg/base_linalg.py +0 -719
  44. trajectree/quimb/quimb/linalg/mpi_launcher.py +0 -397
  45. trajectree/quimb/quimb/linalg/numpy_linalg.py +0 -244
  46. trajectree/quimb/quimb/linalg/rand_linalg.py +0 -514
  47. trajectree/quimb/quimb/linalg/scipy_linalg.py +0 -293
  48. trajectree/quimb/quimb/linalg/slepc_linalg.py +0 -892
  49. trajectree/quimb/quimb/schematic.py +0 -1518
  50. trajectree/quimb/quimb/tensor/__init__.py +0 -401
  51. trajectree/quimb/quimb/tensor/array_ops.py +0 -610
  52. trajectree/quimb/quimb/tensor/circuit.py +0 -4824
  53. trajectree/quimb/quimb/tensor/circuit_gen.py +0 -411
  54. trajectree/quimb/quimb/tensor/contraction.py +0 -336
  55. trajectree/quimb/quimb/tensor/decomp.py +0 -1255
  56. trajectree/quimb/quimb/tensor/drawing.py +0 -1646
  57. trajectree/quimb/quimb/tensor/fitting.py +0 -385
  58. trajectree/quimb/quimb/tensor/geometry.py +0 -583
  59. trajectree/quimb/quimb/tensor/interface.py +0 -114
  60. trajectree/quimb/quimb/tensor/networking.py +0 -1058
  61. trajectree/quimb/quimb/tensor/optimize.py +0 -1818
  62. trajectree/quimb/quimb/tensor/tensor_1d.py +0 -4778
  63. trajectree/quimb/quimb/tensor/tensor_1d_compress.py +0 -1854
  64. trajectree/quimb/quimb/tensor/tensor_1d_tebd.py +0 -662
  65. trajectree/quimb/quimb/tensor/tensor_2d.py +0 -5954
  66. trajectree/quimb/quimb/tensor/tensor_2d_compress.py +0 -96
  67. trajectree/quimb/quimb/tensor/tensor_2d_tebd.py +0 -1230
  68. trajectree/quimb/quimb/tensor/tensor_3d.py +0 -2869
  69. trajectree/quimb/quimb/tensor/tensor_3d_tebd.py +0 -46
  70. trajectree/quimb/quimb/tensor/tensor_approx_spectral.py +0 -60
  71. trajectree/quimb/quimb/tensor/tensor_arbgeom.py +0 -3237
  72. trajectree/quimb/quimb/tensor/tensor_arbgeom_compress.py +0 -565
  73. trajectree/quimb/quimb/tensor/tensor_arbgeom_tebd.py +0 -1138
  74. trajectree/quimb/quimb/tensor/tensor_builder.py +0 -5411
  75. trajectree/quimb/quimb/tensor/tensor_core.py +0 -11179
  76. trajectree/quimb/quimb/tensor/tensor_dmrg.py +0 -1472
  77. trajectree/quimb/quimb/tensor/tensor_mera.py +0 -204
  78. trajectree/quimb/quimb/utils.py +0 -892
  79. trajectree/quimb/tests/__init__.py +0 -0
  80. trajectree/quimb/tests/test_accel.py +0 -501
  81. trajectree/quimb/tests/test_calc.py +0 -788
  82. trajectree/quimb/tests/test_core.py +0 -847
  83. trajectree/quimb/tests/test_evo.py +0 -565
  84. trajectree/quimb/tests/test_gen/__init__.py +0 -0
  85. trajectree/quimb/tests/test_gen/test_operators.py +0 -361
  86. trajectree/quimb/tests/test_gen/test_rand.py +0 -296
  87. trajectree/quimb/tests/test_gen/test_states.py +0 -261
  88. trajectree/quimb/tests/test_linalg/__init__.py +0 -0
  89. trajectree/quimb/tests/test_linalg/test_approx_spectral.py +0 -368
  90. trajectree/quimb/tests/test_linalg/test_base_linalg.py +0 -351
  91. trajectree/quimb/tests/test_linalg/test_mpi_linalg.py +0 -127
  92. trajectree/quimb/tests/test_linalg/test_numpy_linalg.py +0 -84
  93. trajectree/quimb/tests/test_linalg/test_rand_linalg.py +0 -134
  94. trajectree/quimb/tests/test_linalg/test_slepc_linalg.py +0 -283
  95. trajectree/quimb/tests/test_tensor/__init__.py +0 -0
  96. trajectree/quimb/tests/test_tensor/test_belief_propagation/__init__.py +0 -0
  97. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d1bp.py +0 -39
  98. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d2bp.py +0 -67
  99. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hd1bp.py +0 -64
  100. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hv1bp.py +0 -51
  101. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l1bp.py +0 -142
  102. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l2bp.py +0 -101
  103. trajectree/quimb/tests/test_tensor/test_circuit.py +0 -816
  104. trajectree/quimb/tests/test_tensor/test_contract.py +0 -67
  105. trajectree/quimb/tests/test_tensor/test_decomp.py +0 -40
  106. trajectree/quimb/tests/test_tensor/test_mera.py +0 -52
  107. trajectree/quimb/tests/test_tensor/test_optimizers.py +0 -488
  108. trajectree/quimb/tests/test_tensor/test_tensor_1d.py +0 -1171
  109. trajectree/quimb/tests/test_tensor/test_tensor_2d.py +0 -606
  110. trajectree/quimb/tests/test_tensor/test_tensor_2d_tebd.py +0 -144
  111. trajectree/quimb/tests/test_tensor/test_tensor_3d.py +0 -123
  112. trajectree/quimb/tests/test_tensor/test_tensor_arbgeom.py +0 -226
  113. trajectree/quimb/tests/test_tensor/test_tensor_builder.py +0 -441
  114. trajectree/quimb/tests/test_tensor/test_tensor_core.py +0 -2066
  115. trajectree/quimb/tests/test_tensor/test_tensor_dmrg.py +0 -388
  116. trajectree/quimb/tests/test_tensor/test_tensor_spectral_approx.py +0 -63
  117. trajectree/quimb/tests/test_tensor/test_tensor_tebd.py +0 -270
  118. trajectree/quimb/tests/test_utils.py +0 -85
  119. trajectree-0.0.1.dist-info/RECORD +0 -126
  120. {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/WHEEL +0 -0
  121. {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/licenses/LICENSE +0 -0
  122. {trajectree-0.0.1.dist-info → trajectree-0.0.2.dist-info}/top_level.txt +0 -0
@@ -1,1491 +0,0 @@
1
- """Functions for more advanced calculations of quantities and properties of
2
- quantum objects.
3
- """
4
- import numbers
5
- import itertools
6
- import functools
7
- import collections
8
- from math import sin, cos, pi, log, log2, sqrt
9
-
10
- import numpy as np
11
- import numpy.linalg as nla
12
- from scipy.optimize import minimize
13
-
14
- from .core import (
15
- dag,
16
- dop,
17
- dot,
18
- ensure_qarray,
19
- expec,
20
- eye,
21
- ikron,
22
- infer_size,
23
- isop,
24
- issparse,
25
- isvec,
26
- kron,
27
- njit,
28
- prod,
29
- ptr,
30
- qu,
31
- realify,
32
- tr,
33
- zeroify,
34
- )
35
- from .linalg.base_linalg import eigh, eigvalsh, norm, sqrtm, norm_trace_dense
36
- from .linalg.approx_spectral import (
37
- entropy_subsys_approx,
38
- gen_bipartite_spectral_fn,
39
- logneg_subsys_approx,
40
- tr_sqrt_subsys_approx,
41
- )
42
- from .gen.operators import pauli
43
- from .gen.states import basis_vec, bell_state, bloch_state
44
- from .utils import (
45
- frequencies,
46
- int2tup,
47
- keymap,
48
- )
49
-
50
- from .tensor.contraction import array_contract
51
-
52
-
53
- def fidelity(p1, p2, squared=False):
54
- """Fidelity between two quantum states. By default, the unsquared fidelity
55
- is used, equivalent in the pure state case to ``|<psi|phi>|``.
56
-
57
- Parameters
58
- ----------
59
- p1 : vector or operator
60
- First state.
61
- p2 : vector or operator
62
- Second state.
63
- squared : bool, optional
64
- Whether to use the squared convention or not, by default False.
65
-
66
- Returns
67
- -------
68
- float
69
- """
70
- if isvec(p1) or isvec(p2):
71
- F = expec(p1, p2).real
72
- if not squared:
73
- F = max(0.0, F) ** 0.5
74
- return F
75
-
76
- sqrho = sqrtm(p1)
77
- F = tr(sqrtm(dot(sqrho, dot(p2, sqrho)))).real
78
-
79
- if squared:
80
- F = F**2
81
-
82
- return F
83
-
84
-
85
- def purify(rho):
86
- """Take state rho and purify it into a wavefunction of squared
87
- dimension.
88
-
89
- Parameters
90
- ----------
91
- rho : operator
92
- Density operator to purify.
93
-
94
- Returns
95
- -------
96
- vector :
97
- The purified ket.
98
- """
99
- d = rho.shape[0]
100
- evals, vs = eigh(rho)
101
- evals = np.sqrt(np.clip(evals, 0, 1))
102
- psi = np.zeros(shape=(d**2, 1), dtype=complex)
103
- for i, evals in enumerate(evals.flat):
104
- psi += evals * kron(vs[:, [i]], basis_vec(i, d))
105
- return qu(psi)
106
-
107
-
108
- @ensure_qarray
109
- def kraus_op(rho, Ek, dims=None, where=None, check=False):
110
- r"""Operate on a mixed state with a set of kraus operators:
111
-
112
- .. math::
113
-
114
- \sigma = \sum_k E_k \rho E_k^{\dagger}
115
-
116
- Parameters
117
- ----------
118
- rho : (d, d) density matrix
119
- The density operator to perform operation on.
120
- Ek : (K, d, d) array or sequence of K (d, d) arrays
121
- The Kraus operator(s).
122
- dims : sequence of int, optional
123
- The subdimensions of ``rho``.
124
- where : int or sequence of int, optional
125
- Which of the subsystems to apply the operation on.
126
- check : bool, optional
127
- Where to check ``sum_k(Ek.H @ Ek) == 1``.
128
-
129
- Returns
130
- -------
131
- sigma : density matrix
132
- The state after the kraus operation.
133
-
134
- Examples
135
- --------
136
- The depolarising channel:
137
-
138
- .. code-block:: python3
139
-
140
- In [1]: import quimb as qu
141
-
142
- In [2]: rho = qu.rand_rho(2)
143
-
144
- In [3]: I, X, Y, Z = (qu.pauli(s) for s in 'IXYZ')
145
-
146
- In [4]: [qu.expec(rho, A) for A in (X, Y, Z)]
147
- Out[4]: [-0.652176185230622, -0.1301762132792484, 0.22362918368272583]
148
-
149
- In [5]: p = 0.1
150
-
151
- In [6]: Ek = [
152
- ...: (1 - p)**0.5 * I,
153
- ...: (p / 3)**0.5 * X,
154
- ...: (p / 3)**0.5 * Y,
155
- ...: (p / 3)**0.5 * Z
156
- ...: ]
157
-
158
- In [7]: sigma = qu.kraus_op(rho, Ek)
159
-
160
- In [8]: [qu.expec(sigma, A) for A in (X, Y, Z)]
161
- Out[8]: [-0.5652193605332058, -0.11281938484201527, 0.1938119591916957]
162
- """
163
- if not isinstance(Ek, np.ndarray):
164
- Ek = np.stack(Ek, axis=0)
165
-
166
- if check:
167
- SEk = np.einsum("kij,kil", Ek.conj(), Ek)
168
- if norm(SEk - eye(Ek.shape[-1]), "fro") > 1e-12:
169
- raise ValueError("Did not find ``sum(E_k.H @ Ek) == 1``.")
170
-
171
- if int(dims is None) + int(where is None) == 1:
172
- raise ValueError("If `dims` is specified so should `where`.")
173
-
174
- if isinstance(where, numbers.Integral):
175
- where = (where,)
176
-
177
- if dims:
178
- # the full subdimensions of ``rho``
179
- dims = tuple(dims)
180
- rho = rho.reshape(dims + dims)
181
- N = len(dims)
182
-
183
- # the subdimensions of where the kraus operator should act
184
- kdims = tuple(dims[i] for i in where)
185
- Ek = Ek.reshape((-1,) + kdims + kdims)
186
-
187
- rho_inds = (
188
- *(f"i*{q}" if q in where else f"i{q}" for q in range(N)),
189
- *(f"j*{q}" if q in where else f"j{q}" for q in range(N)),
190
- )
191
- Ei_inds = ("K", *(f"i{q}" for q in where), *(f"i*{q}" for q in where))
192
- Ej_inds = ("K", *(f"j{q}" for q in where), *(f"j*{q}" for q in where))
193
- out = (*(f"i{q}" for q in range(N)), *(f"j{q}" for q in range(N)))
194
- else:
195
- Ei_inds = ("K", "i", "i*")
196
- rho_inds = ("i*", "j*")
197
- Ej_inds = ("K", "j", "j*")
198
- out = ("i", "j")
199
-
200
- sigma = array_contract(
201
- (Ek, rho, Ek.conj()),
202
- (Ei_inds, rho_inds, Ej_inds),
203
- out,
204
- )
205
-
206
- if dims:
207
- sigma = sigma.reshape(prod(dims), prod(dims))
208
-
209
- return sigma
210
-
211
-
212
- @ensure_qarray
213
- def projector(A, eigenvalue=1.0, tol=1e-12, autoblock=False):
214
- """Compute the projector for the target ``eigenvalue`` of operator
215
- ``A``.
216
-
217
- Parameters
218
- ----------
219
- A : operator or tuple[array, operator].
220
- The hermitian observable. If a tuple is supplied, assume this is the
221
- eigendecomposition of the observable.
222
- eigenvalue : float, optional
223
- The target eigenvalue to construct the projector for, default: 1.
224
- tol : float, optional
225
- The tolerance with which to select all eigenvalues.
226
- autoblock : bool, optional
227
- Whether to use automatic symmetry exploitation.
228
-
229
- Returns
230
- -------
231
- P : dense matrix
232
- The projector onto the target eigenspace.
233
- """
234
- if isinstance(A, (tuple, list)):
235
- el, ev = A
236
- else:
237
- el, ev = eigh(A, autoblock=autoblock)
238
- which = np.argwhere(abs(el - eigenvalue) < tol)
239
- P = np.zeros_like(ev)
240
- for i in which:
241
- vi = ev[:, i]
242
- P += vi @ vi.H
243
- return P
244
-
245
-
246
- def measure(p, A, eigenvalue=None, tol=1e-12):
247
- r"""Measure state ``p`` with observable ``A``, collapsing the state in the
248
- process and returning the relevant eigenvalue.
249
-
250
- .. math::
251
-
252
- \left| \psi \right\rangle \rightarrow
253
- \frac{
254
- P_i \left| \psi \right\rangle
255
- }{
256
- \sqrt{\left\langle \psi \right| P_i \left| \psi \right\rangle}
257
- }
258
-
259
- and
260
-
261
- .. math::
262
-
263
- \rho \rightarrow
264
- \frac{
265
- P_i \rho P_i^{\dagger}
266
- }{
267
- \text{Tr} \left[ P_i \rho \right]
268
- }
269
-
270
- along with the corresponding eigenvalue :math:`\lambda_i`.
271
-
272
- Parameters
273
- ----------
274
- p : vector or matrix
275
- The quantum state to measure.
276
- A : matrix or tuple[array, matrix]
277
- The hermitian operator corresponding to an observable. You can supply
278
- also a pre-diagonalized operator here as a tuple of eigenvalues and
279
- eigenvectors.
280
- tol : float, optional
281
- The tolerance within which to group eigenspaces.
282
- eigenvalue : float, optional
283
- If specified, deterministically collapse to this result. Otherwise
284
- randomly choose a result as in 'real-life'.
285
-
286
- Returns
287
- -------
288
- result : float
289
- The result of the measurement.
290
- p_after : vector or matrix
291
- The quantum state post measurement.
292
- """
293
- if isinstance(A, (tuple, list)):
294
- el, ev = A
295
- else:
296
- el, ev = eigh(A)
297
-
298
- js = np.arange(el.size)
299
-
300
- # compute prob of each eigenvector
301
- if isvec(p):
302
- pj = (abs(ev.H @ p) ** 2).flatten()
303
- else:
304
- pj = array_contract(
305
- (ev.H, p, ev),
306
- ("jk", "kl", "lj"),
307
- ("j",),
308
- ).real
309
-
310
- # then choose one
311
- if eigenvalue is None:
312
- j = np.random.choice(js, p=pj)
313
- eigenvalue = el[j]
314
-
315
- # now combine whole eigenspace
316
- P = projector((el, ev), eigenvalue=eigenvalue, tol=tol)
317
- total_prob = np.sum(pj[abs(el - eigenvalue) < tol])
318
-
319
- # now collapse the state
320
- if isvec(p):
321
- p_after = P @ (p / total_prob**0.5)
322
- else:
323
- p_after = (P @ p @ P.H) / total_prob
324
-
325
- return eigenvalue, p_after
326
-
327
-
328
- def simulate_counts(p, C, phys_dim=2, seed=None):
329
- """Simulate measuring each qubit of ``p`` in the computational basis,
330
- producing output like that of ``qiskit``.
331
-
332
- Parameters
333
- ----------
334
- p : vector or operator
335
- The quantum state, assumed to be normalized, as either a ket or density
336
- operator.
337
- C : int
338
- The number of counts to perform.
339
- phys_dim : int, optional
340
- The assumed size of the subsystems of ``p``, defaults to 2 for qubits.
341
-
342
- Returns
343
- -------
344
- results : dict[str, int]
345
- The counts for each bit string measured.
346
-
347
- Examples
348
- --------
349
-
350
- Simulate measuring the state of each qubit in a GHZ-state:
351
-
352
- .. code:: python3
353
-
354
- >>> import quimb as qu
355
- >>> psi = qu.ghz_state(3)
356
- >>> qu.simulate_counts(psi, 1024)
357
- {'000': 514, '111': 510}
358
-
359
- """
360
- rng = np.random.default_rng(seed)
361
-
362
- n = infer_size(p, phys_dim)
363
- d = phys_dim**n
364
-
365
- if isop(p):
366
- pi = np.diag(p).real
367
- else:
368
- pi = np.multiply(np.conj(p), p).real
369
-
370
- # probability of each basis state
371
- pi = pi.reshape(-1)
372
-
373
- # raw counts in terms of integers
374
- raw_counts = rng.choice(d, size=C, p=pi)
375
-
376
- # convert to frequencies of binary
377
- bin_str = "{:0>" + str(n) + "b}"
378
- results = keymap(bin_str.format, frequencies(raw_counts))
379
-
380
- return results
381
-
382
-
383
- def dephase(rho, p, rand_rank=None):
384
- """Dephase ``rho`` by amount ``p``, that is, mix it
385
- with the maximally mixed state:
386
-
387
- rho -> (1 - p) * rho + p * I / d
388
-
389
- Parameters
390
- ----------
391
- rho : operator
392
- The state.
393
- p : float
394
- The final proportion of identity.
395
- rand_rank : int or float, optional
396
- If given, dephase with a random diagonal operator with this many
397
- non-zero entries. If float, proportion of full size.
398
-
399
- Returns
400
- -------
401
- rho_dephase : operator
402
- The dephased density operator.
403
- """
404
- d = rho.shape[0]
405
-
406
- if (rand_rank is None) or (rand_rank == d) or (rand_rank == 1.0):
407
- dephaser = eye(d) / d
408
-
409
- else:
410
- if not isinstance(rand_rank, numbers.Integral):
411
- rand_rank = int(rand_rank * d)
412
- rand_rank = min(max(1, rand_rank), d)
413
-
414
- dephaser = np.zeros((d, d))
415
- dephaser_diag = np.einsum("aa->a", dephaser)
416
- nnz = np.random.choice(np.arange(d), size=rand_rank, replace=False)
417
- dephaser_diag[nnz] = 1 / rand_rank
418
-
419
- return (1 - p) * rho + p * dephaser
420
-
421
-
422
- @zeroify
423
- def entropy(a, rank=None):
424
- """Compute the (von Neumann) entropy.
425
-
426
- Parameters
427
- ----------
428
- a : operator or 1d array
429
- Positive operator or list of positive eigenvalues.
430
- rank : int (optional)
431
- If operator has known rank, then a partial decomposition can be
432
- used to accelerate the calculation.
433
-
434
- Returns
435
- -------
436
- float
437
- The von Neumann entropy.
438
-
439
- See Also
440
- --------
441
- mutinf, entropy_subsys, entropy_subsys_approx
442
- """
443
- a = np.asarray(a)
444
- if np.ndim(a) == 1:
445
- evals = a
446
- else:
447
- if rank is None:
448
- evals = eigvalsh(a)
449
- else: # know that not all eigenvalues needed
450
- evals = eigvalsh(a, k=rank, which="LM", backend="AUTO")
451
-
452
- evals = evals[evals > 0.0]
453
- return np.sum(-evals * np.log2(evals))
454
-
455
-
456
- entropy_subsys = gen_bipartite_spectral_fn(entropy, entropy_subsys_approx, 0.0)
457
- """Calculate the entropy of a pure states' subsystem, optionally switching
458
- to an approximate lanczos method when the subsystem is very large.
459
-
460
- Parameters
461
- ----------
462
- psi_ab : vector
463
- Bipartite state.
464
- dims : sequence of int
465
- The sub-dimensions of the state.
466
- sysa : sequence of int
467
- The indices of which dimensions to calculate the entropy for.
468
- approx_thresh : int, optional
469
- The size of sysa at which to switch to the approx method. Set to
470
- ``None`` to never use the approximation.
471
- **approx_opts
472
- Supplied to :func:`entropy_subsys_approx`, if used.
473
-
474
- Returns
475
- -------
476
- float
477
- The subsytem entropy.
478
-
479
- See Also
480
- --------
481
- entropy, entropy_subsys_approx, mutinf_subsys
482
- """
483
-
484
-
485
- @zeroify
486
- def mutinf(p, dims=(2, 2), sysa=0, rank=None):
487
- """Find the mutual information for a bipartition of a state.
488
-
489
- That is, ``H(A) + H(B) - H(AB)``, for von Neumann entropy ``H``, and two
490
- subsystems A and B.
491
-
492
- Parameters
493
- ----------
494
- p : vector or operator
495
- State, can be vector or operator.
496
- dims : tuple(int), optional
497
- Internal dimensions of state.
498
- sysa : int, optional
499
- Index of first subsystem, A.
500
- sysb : int, optional
501
- Index of second subsystem, B.
502
- rank : int, optional
503
- If known, the rank of rho_ab, to speed calculation of ``H(AB)`` up.
504
- For example, if ``p`` comes from tracing out three qubits from a
505
- system, then its rank is 2^3 = 8 etc.
506
-
507
- Returns
508
- -------
509
- float
510
-
511
- See Also
512
- --------
513
- entropy, mutinf_subsys, entropy_subsys_approx
514
- """
515
- sysa = int2tup(sysa)
516
-
517
- # mixed combined system
518
- if isop(p):
519
- # total
520
- hab = entropy(p, rank=rank)
521
-
522
- # subsystem a
523
- rhoa = ptr(p, dims, sysa)
524
- ha = entropy(rhoa)
525
-
526
- # need subsystem b as well
527
- sysb = tuple(i for i in range(len(dims)) if i not in sysa)
528
- rhob = ptr(p, dims, sysb)
529
- hb = entropy(rhob)
530
-
531
- # pure combined system
532
- else:
533
- hab = 0.0
534
- ha = hb = entropy_subsys(p, dims, sysa)
535
-
536
- return ha + hb - hab
537
-
538
-
539
- mutual_information = mutinf
540
-
541
-
542
- def check_dims_and_indices(dims, *syss):
543
- """Make sure all indices found in the tuples ``syss`` are in
544
- ``range(len(dims))``.
545
- """
546
- nsys = len(dims)
547
- all_sys = sum(syss, ())
548
-
549
- if not all(0 <= i < nsys for i in all_sys):
550
- raise ValueError(
551
- "Indices specified in `sysa` and `sysb` must be "
552
- f"in range({nsys}) for dims {dims}."
553
- )
554
-
555
-
556
- def mutinf_subsys(
557
- psi_abc, dims, sysa, sysb, approx_thresh=2**13, **approx_opts
558
- ):
559
- """Calculate the mutual information of two subsystems of a pure state,
560
- possibly using an approximate lanczos method for large subsytems.
561
-
562
- Parameters
563
- ----------
564
- psi_abc : vector
565
- Tri-partite pure state.
566
- dims : sequence of int
567
- The sub dimensions of the state.
568
- sysa : sequence of int
569
- The index(es) of the subsystem(s) to consider part of 'A'.
570
- sysb : sequence of int
571
- The index(es) of the subsystem(s) to consider part of 'B'.
572
- approx_thresh : int, optional
573
- The size of subsystem at which to switch to the approximate lanczos
574
- method. Set to ``None`` to never use the approximation.
575
- approx_opts
576
- Supplied to :func:`entropy_subsys_approx`, if used.
577
-
578
- Returns
579
- -------
580
- float
581
- The mutual information.
582
-
583
- See Also
584
- --------
585
- mutinf, entropy_subsys, entropy_subsys_approx, logneg_subsys
586
- """
587
- sysa, sysb = int2tup(sysa), int2tup(sysb)
588
-
589
- check_dims_and_indices(dims, sysa, sysb)
590
-
591
- sz_a = prod(d for i, d in enumerate(dims) if i in sysa)
592
- sz_b = prod(d for i, d in enumerate(dims) if i in sysb)
593
- sz_c = prod(dims) // (sz_a * sz_b)
594
-
595
- kws = {"approx_thresh": approx_thresh, **approx_opts}
596
-
597
- if sz_c == 1:
598
- hab = 0.0
599
- ha = hb = entropy_subsys(psi_abc, dims, sysa, **kws)
600
- else:
601
- hab = entropy_subsys(psi_abc, dims, sysa + sysb, **kws)
602
- ha = entropy_subsys(psi_abc, dims, sysa, **kws)
603
- hb = entropy_subsys(psi_abc, dims, sysb, **kws)
604
-
605
- return hb + ha - hab
606
-
607
-
608
- def schmidt_gap(psi_ab, dims, sysa):
609
- """Find the schmidt gap of the bipartition of ``psi_ab``. That is, the
610
- difference between the two largest eigenvalues of the reduced density
611
- operator.
612
-
613
- Parameters
614
- ----------
615
- psi_ab : vector
616
- Bipartite state.
617
- dims : sequence of int
618
- The sub-dimensions of the state.
619
- sysa : sequence of int
620
- The indices of which dimensions to calculate the entropy for.
621
-
622
- Returns
623
- -------
624
- float
625
- """
626
- sysa = int2tup(sysa)
627
- sz_a = prod(d for i, d in enumerate(dims) if i in sysa)
628
- sz_b = prod(dims) // sz_a
629
-
630
- # pure state
631
- if sz_b == 1:
632
- return 1.0
633
-
634
- # also check if system b is smaller, since spectrum is same for both
635
- if sz_b < sz_a:
636
- # if so swap things around
637
- sysb = [i for i in range(len(dims)) if i not in sysa]
638
- sysa = sysb
639
-
640
- rho_a = ptr(psi_ab, dims, sysa)
641
- el = eigvalsh(rho_a, k=2, which="LM")
642
- return abs(el[0] - el[1])
643
-
644
-
645
- def tr_sqrt(A, rank=None):
646
- """Return the trace of the sqrt of a positive semidefinite operator."""
647
- if rank is None:
648
- el = eigvalsh(A, sort=False)
649
- else:
650
- el = eigvalsh(A, k=rank, which="LM", backend="AUTO")
651
- return np.sum(np.sqrt(el[el > 0.0]))
652
-
653
-
654
- tr_sqrt_subsys = gen_bipartite_spectral_fn(tr_sqrt, tr_sqrt_subsys_approx, 1.0)
655
- """Compute the trace sqrt of a subsystem, possibly using an approximate
656
- lanczos method when the subsytem is big.
657
-
658
- Parameters
659
- ----------
660
- psi_ab : vector
661
- Bipartite state.
662
- dims : sequence of int
663
- The sub-dimensions of the state.
664
- sysa : sequence of int
665
- The indices of which dimensions to calculate the trace sqrt for.
666
- approx_thresh : int, optional
667
- The size of sysa at which to switch to the approx method. Set to
668
- ``None`` to never use the approximation.
669
- **approx_opts
670
- Supplied to :func:`tr_sqrt_subsys_approx`, if used.
671
-
672
- Returns
673
- -------
674
- float
675
- The subsytem entropy.
676
-
677
- See Also
678
- --------
679
- tr_sqrt, tr_sqrt_subsys_approx, partial_transpose_norm
680
- """
681
-
682
-
683
- @ensure_qarray
684
- def partial_transpose(p, dims=(2, 2), sysa=0):
685
- """Partial transpose of a density operator.
686
-
687
- Parameters
688
- ----------
689
- p : operator or vector
690
- The state to partially transpose.
691
- dims : tuple(int), optional
692
- The internal dimensions of the state.
693
- sysa : sequence of int
694
- The indices of 'system A', everything else assumed to be 'system B'.
695
-
696
- Returns
697
- -------
698
- operator
699
-
700
- See Also
701
- --------
702
- logneg, negativity
703
- """
704
- sysa = int2tup(sysa)
705
-
706
- ndims = len(dims)
707
- perm_ket_inds = []
708
- perm_bra_inds = []
709
-
710
- for i in range(ndims):
711
- if i in sysa:
712
- perm_ket_inds.append(i + ndims)
713
- perm_bra_inds.append(i)
714
- else:
715
- perm_ket_inds.append(i)
716
- perm_bra_inds.append(i + ndims)
717
-
718
- return (
719
- np.asarray(qu(p, "dop"))
720
- .reshape((*dims, *dims))
721
- .transpose((*perm_ket_inds, *perm_bra_inds))
722
- .reshape((prod(dims), prod(dims)))
723
- )
724
-
725
-
726
- def partial_transpose_norm(p, dims, sysa):
727
- """Compute the norm of the partial transpose for (log)-negativity,
728
- taking a shortcut (trace sqrt of reduced subsytem), when system is a
729
- vector.
730
- """
731
- sysa = int2tup(sysa)
732
-
733
- # check for pure bipartition -> easier to calc
734
- if isvec(p):
735
- sz_a = prod(d for i, d in enumerate(dims) if i in sysa)
736
- sz_b = prod(dims) // sz_a
737
-
738
- # check if system b is smaller, since entropy is same for both a & b.
739
- if sz_b < sz_a:
740
- # if so swap things around
741
- sysb = [i for i in range(len(dims)) if i not in sysa]
742
- sysa = sysb
743
-
744
- rhoa = ptr(p, dims, sysa)
745
- return tr_sqrt(rhoa) ** 2
746
-
747
- return norm_trace_dense(partial_transpose(p, dims, sysa), isherm=True)
748
-
749
-
750
- @zeroify
751
- def logneg(p, dims=(2, 2), sysa=0):
752
- """Compute logarithmic negativity between two subsytems.
753
- This is defined as log_2( | rho_{AB}^{T_B} | ). This only handles
754
- bipartitions (including pure states efficiently), and will not trace
755
- anything out.
756
-
757
- Parameters
758
- ----------
759
- p : ket vector or density operator
760
- State to compute logarithmic negativity for.
761
- dims : tuple(int), optional
762
- The internal dimensions of ``p``.
763
- sysa : int, optional
764
- Index of the first subsystem, A, relative to ``dims``.
765
-
766
- Returns
767
- -------
768
- float
769
-
770
- See Also
771
- --------
772
- negativity, partial_transpose, logneg_subsys_approx
773
- """
774
- return max(0.0, log2(partial_transpose_norm(p, dims, sysa)))
775
-
776
-
777
- logarithmic_negativity = logneg
778
-
779
-
780
- def logneg_subsys(
781
- psi_abc, dims, sysa, sysb, approx_thresh=2**13, **approx_opts
782
- ):
783
- """Compute the logarithmic negativity between two subsystems of a pure
784
- state, possibly using an approximate lanczos for large subsystems. Uses
785
- a special method if the two subsystems form a bipartition of the state.
786
-
787
- Parameters
788
- ----------
789
- psi_abc : vector
790
- Tri-partite pure state.
791
- dims : sequence of int
792
- The sub dimensions of the state.
793
- sysa : sequence of int
794
- The index(es) of the subsystem(s) to consider part of 'A'.
795
- sysb : sequence of int
796
- The index(es) of the subsystem(s) to consider part of 'B'.
797
- approx_thresh : int, optional
798
- The size of subsystem at which to switch to the approximate lanczos
799
- method. Set to ``None`` to never use the approximation.
800
- approx_opts
801
- Supplied to :func:`~quimb.logneg_subsys_approx`, if used.
802
-
803
- Returns
804
- -------
805
- float
806
- The logarithmic negativity.
807
-
808
- See Also
809
- --------
810
- logneg, mutinf_subsys, logneg_subsys_approx
811
- """
812
- sysa, sysb = int2tup(sysa), int2tup(sysb)
813
-
814
- check_dims_and_indices(dims, sysa, sysb)
815
-
816
- sz_a = prod(d for i, d in enumerate(dims) if i in sysa)
817
- sz_b = prod(d for i, d in enumerate(dims) if i in sysb)
818
- sz_ab = sz_a * sz_b
819
- sz_c = prod(dims) // sz_ab
820
-
821
- # check for pure bipartition
822
- if sz_c == 1:
823
- psi_ab_ppt_norm = (
824
- tr_sqrt_subsys(
825
- psi_abc, dims, sysa, approx_thresh=approx_thresh, **approx_opts
826
- )
827
- ** 2
828
- )
829
- return max(log2(psi_ab_ppt_norm), 0.0)
830
-
831
- # check whether to use approx lanczos method
832
- if (approx_thresh is not None) and (sz_ab >= approx_thresh):
833
- return logneg_subsys_approx(psi_abc, dims, sysa, sysb, **approx_opts)
834
-
835
- rho_ab = ptr(psi_abc, dims, sysa + sysb)
836
-
837
- # need to adjust for new dimensions and indices
838
- new_dims, new_sysa = [], []
839
- new_inds = iter(range(len(dims)))
840
-
841
- for i, d in enumerate(dims):
842
- if i in sysa:
843
- new_dims.append(d)
844
- new_sysa.append(next(new_inds))
845
- elif i in sysb:
846
- new_dims.append(d)
847
- next(new_inds) # don't need sysb
848
-
849
- return logneg(rho_ab, new_dims, new_sysa)
850
-
851
-
852
- def negativity(p, dims=(2, 2), sysa=0):
853
- """Compute negativity between two subsytems.
854
-
855
- This is defined as (| rho_{AB}^{T_B} | - 1) / 2. If ``len(dims) > 2``,
856
- then the non-target dimensions will be traced out first.
857
-
858
- Parameters
859
- ----------
860
- p : ket vector or density operator
861
- State to compute logarithmic negativity for.
862
- dims : tuple(int), optional
863
- The internal dimensions of ``p``.
864
- sysa : int, optional
865
- Index of the first subsystem, A, relative to ``dims``.
866
-
867
- Returns
868
- -------
869
- float
870
-
871
- See Also
872
- --------
873
- logneg, partial_transpose, negativity_subsys_approx
874
- """
875
- return max(0.0, (partial_transpose_norm(p, dims, sysa) - 1) / 2)
876
-
877
-
878
- @zeroify
879
- def concurrence(p, dims=(2, 2), sysa=0, sysb=1):
880
- """Concurrence of two-qubit state.
881
-
882
- If ``len(dims) > 2``, then the non-target dimensions will be traced out
883
- first.
884
-
885
- Parameters
886
- ----------
887
- p : ket vector or density operator
888
- State to compute concurrence for.
889
- dims : tuple(int), optional
890
- The internal dimensions of ``p``.
891
- sysa : int, optional
892
- Index of the first subsystem, A, relative to ``dims``.
893
- sysb : int, optional
894
- Index of the first subsystem, B, relative to ``dims``.
895
-
896
- Returns
897
- -------
898
- float
899
- """
900
- if len(dims) > 2:
901
- p = ptr(p, dims, (sysa, sysb))
902
-
903
- Y = pauli("Y")
904
-
905
- if isop(p):
906
- pt = dot(kron(Y, Y), dot(p.conj(), kron(Y, Y)))
907
- evals = (nla.eigvals(dot(p, pt)).real ** 2) ** 0.25
908
- return max(0, 2 * np.max(evals) - np.sum(evals))
909
- else:
910
- pt = dot(kron(Y, Y), p.conj())
911
- c = np.real(abs(dot(dag(p), pt))).item(0)
912
- return max(0, c)
913
-
914
-
915
- def one_way_classical_information(p_ab, prjs, precomp_func=False):
916
- """One way classical information for two qubit density operator.
917
-
918
- Parameters
919
- ----------
920
- p_ab : operator
921
- State of two qubits
922
- prjs : sequence of matrices
923
- The POVMs.
924
- precomp_func : bool, optional
925
- Whether to return a pre-computed function, closed over the actual
926
- state.
927
-
928
- Returns
929
- -------
930
- float or callable
931
- The one-way classical information or the function to compute it for
932
- the given state which takes a set of POVMs as its single argument.
933
- """
934
- p_a = ptr(p_ab, (2, 2), 0)
935
- s_a = entropy(p_a)
936
-
937
- def owci(prjs):
938
- def gen_paj():
939
- for prj in prjs:
940
- p_ab_j = dot((eye(2) & prj), p_ab)
941
- prob = tr(p_ab_j)
942
- p_a_j = ptr(p_ab_j, (2, 2), 0) / prob
943
- yield prob, p_a_j
944
-
945
- return s_a - sum(p * entropy(rho) for p, rho in gen_paj())
946
-
947
- return owci if precomp_func else owci(prjs)
948
-
949
-
950
- @zeroify
951
- def quantum_discord(
952
- p,
953
- dims=(2, 2),
954
- sysa=0,
955
- sysb=1,
956
- method="COBYLA",
957
- tol=1e-12,
958
- maxiter=2**14,
959
- ):
960
- """Quantum Discord for two qubit density operator.
961
-
962
- If ``len(dims) > 2``, then the non-target dimensions will be traced out
963
- first.
964
-
965
- Parameters
966
- ----------
967
- p : ket vector or density operator
968
- State to compute quantum discord for.
969
- dims : tuple(int), optional
970
- The internal dimensions of ``p``.
971
- sysa : int, optional
972
- Index of the first subsystem, A, relative to ``dims``.
973
- sysb : int, optional
974
- Index of the first subsystem, B, relative to ``dims``.
975
-
976
- Returns
977
- -------
978
- float
979
- """
980
- if len(dims) > 2:
981
- p = ptr(p, dims, (sysa, sysb))
982
- else:
983
- p = qu(p, "dop")
984
- iab = mutual_information(p)
985
- owci = one_way_classical_information(p, None, precomp_func=True)
986
-
987
- def trial_qd(a):
988
- ax, ay, az = sin(a[0]) * cos(a[1]), sin(a[0]) * sin(a[1]), cos(a[0])
989
- prja = bloch_state(ax, ay, az)
990
- prjb = eye(2) - prja
991
- return iab - owci((prja, prjb))
992
-
993
- opt = minimize(
994
- trial_qd,
995
- (pi / 2, pi),
996
- method=method,
997
- bounds=((0, pi), (0, 2 * pi)),
998
- tol=tol,
999
- options=dict(
1000
- maxiter=maxiter,
1001
- ),
1002
- )
1003
- if opt.success:
1004
- return opt.fun
1005
- else: # pragma: no cover
1006
- raise ValueError(opt.message)
1007
-
1008
-
1009
- @zeroify
1010
- def trace_distance(p1, p2):
1011
- r"""Trace distance between two states:
1012
-
1013
- .. math::
1014
-
1015
- \delta(\rho, \sigma)
1016
- =
1017
- \frac{1}{2} \left| \rho - \sigma \right|_\mathrm{tr}
1018
-
1019
- If two wavefunctions are supplied the trace distance will be computed via
1020
- the more efficient expression:
1021
-
1022
- .. math::
1023
-
1024
- \delta(|\psi\rangle\langle\psi|, |\phi\rangle\langle\phi|)
1025
- =
1026
- \sqrt{1 - \langle \psi | \phi \rangle^2}
1027
-
1028
- Parameters
1029
- ----------
1030
- p1 : ket or density operator
1031
- The first state.
1032
- p2 : ket or density operator
1033
- The second state.
1034
-
1035
- Returns
1036
- -------
1037
- float
1038
- """
1039
- p1_is_op, p2_is_op = isop(p1), isop(p2)
1040
-
1041
- # If both are pure kets then special case
1042
- if (not p1_is_op) and (not p2_is_op):
1043
- return sqrt(1 - expec(p1, p2))
1044
-
1045
- # Otherwise do full calculation
1046
- return 0.5 * norm(
1047
- (p1 if p1_is_op else dop(p1)) - (p2 if p2_is_op else dop(p2)), "tr"
1048
- )
1049
-
1050
-
1051
- def cprint(psi, prec=6):
1052
- """Print a state in the computational basis.
1053
-
1054
- Parameters
1055
- ----------
1056
- psi : vector
1057
- The pure state.
1058
- prec : int, optional
1059
- How many significant figures to print.
1060
-
1061
- Examples
1062
- --------
1063
-
1064
- >>> cprint(rand_ket(2**2))
1065
- (-0.0751069-0.192635j) |00>
1066
- (0.156837-0.562213j) |01>
1067
- (-0.307381+0.0291168j) |10>
1068
- (-0.14302+0.707661j) |11>
1069
-
1070
- >>> cprint(w_state(4))
1071
- (0.5+0j) |0001>
1072
- (0.5+0j) |0010>
1073
- (0.5+0j) |0100>
1074
- (0.5+0j) |1000>
1075
- """
1076
- d = psi.shape[0]
1077
- n = int(log2(d))
1078
- if 2**n != d:
1079
- raise ValueError("State is not factorizable into computational basis.")
1080
-
1081
- coeff_str = "{:." + str(prec) + "}"
1082
-
1083
- coeffs, cs = [], []
1084
- for c in itertools.product("01", repeat=n):
1085
- c = "".join(c)
1086
- ix = int(c, 2)
1087
- coeff = psi.item(ix)
1088
- if coeff != 0.0:
1089
- cs.append(c)
1090
- coeffs.append(coeff_str.format(coeff))
1091
-
1092
- max_str_len = max(map(len, coeffs))
1093
- full_str = "{:>" + str(max_str_len) + "} |{}>"
1094
- for coeff, c in zip(coeffs, cs):
1095
- print(full_str.format(coeff, c))
1096
-
1097
-
1098
- def decomp(a, fn, fn_args, fn_d, nmlz_func, mode="p", tol=1e-3):
1099
- """Decomposes an operator via the Hilbert-schmidt inner product.
1100
-
1101
- Can both print the decomposition or return it.
1102
-
1103
- Parameters
1104
- ----------
1105
- a : ket or density operator
1106
- Operator to decompose.
1107
- fn : callable
1108
- Function to generate operator/state to decompose with.
1109
- fn_args :
1110
- Sequence of args whose permutations will be supplied to ``fn``.
1111
- fn_d : int
1112
- The dimension of the operators that `fn` produces.
1113
- nmlz_func : callable
1114
- Function to produce a normlization coefficient given the ``n``
1115
- permutations of operators that will be produced.
1116
- mode :
1117
- String, include ``'p'`` to print the decomp and/or ``'c'`` to
1118
- return OrderedDict, sorted by size of contribution.
1119
- tol :
1120
- Print operators with contirbution above ``tol`` only.
1121
-
1122
- Returns
1123
- -------
1124
- None or OrderedDict:
1125
- Pauli operator name and expec with ``a``.
1126
-
1127
- See Also
1128
- --------
1129
- pauli_decomp, bell_decomp
1130
- """
1131
- if isvec(a):
1132
- a = qu(a, "dop") # make sure operator
1133
- n = infer_size(a, base=fn_d)
1134
-
1135
- # define generator for inner product to iterate over efficiently
1136
- def calc_name_and_overlap():
1137
- for perm in itertools.product(fn_args, repeat=n):
1138
- op = kron(*(fn(x, sparse=True) for x in perm)) * nmlz_func(n)
1139
- cff = expec(a, op)
1140
- yield "".join(str(x) for x in perm), cff
1141
-
1142
- names_cffs = list(calc_name_and_overlap())
1143
- # sort by descending expec and turn into OrderedDict
1144
- names_cffs.sort(key=lambda pair: -abs(pair[1]))
1145
- names_cffs = collections.OrderedDict(names_cffs)
1146
- # Print decomposition
1147
- if "p" in mode:
1148
- for x, cff in names_cffs.items():
1149
- if abs(cff) < 0.01:
1150
- break
1151
- dps = int(round(0.5 - np.log10(1.001 * tol))) # decimal places
1152
- print(x, "{: .{prec}f}".format(cff, prec=dps))
1153
- # Return full calculation
1154
- if "c" in mode:
1155
- return names_cffs
1156
-
1157
-
1158
- pauli_decomp = functools.partial(
1159
- decomp, fn=pauli, fn_args="IXYZ", fn_d=2, nmlz_func=lambda n: 2**-n
1160
- )
1161
- """Decompose an operator into Paulis."""
1162
-
1163
- bell_decomp = functools.partial(
1164
- decomp, fn=bell_state, fn_args=(0, 1, 2, 3), fn_d=4, nmlz_func=lambda x: 1
1165
- )
1166
- """Decompose an operator into bell-states."""
1167
-
1168
-
1169
- def correlation(
1170
- p, A, B, sysa, sysb, dims=None, sparse=None, precomp_func=False
1171
- ):
1172
- """Calculate the correlation between two sites given two operators.
1173
-
1174
- Parameters
1175
- ----------
1176
- p : ket or density operator
1177
- State to compute correlations for, ignored if ``precomp_func=True``.
1178
- A : operator
1179
- Operator to act on first subsystem.
1180
- B : operator
1181
- Operator to act on second subsystem.
1182
- sysa : int
1183
- Index of first subsystem.
1184
- sysb : int
1185
- Index of second subsystem.
1186
- dims : tuple of int, optional
1187
- Internal dimensions of ``p``, will be assumed to be qubits if not
1188
- given.
1189
- sparse : bool, optional
1190
- Whether to compute with sparse operators.
1191
- precomp_func : bool, optional
1192
- Whether to return result or single arg function closed
1193
- over precomputed operator.
1194
-
1195
- Returns
1196
- -------
1197
- float or callable
1198
- The correlation, <ab> - <a><b>, or a function to compute for an
1199
- arbitrary state.
1200
- """
1201
- if dims is None:
1202
- sz_p = infer_size(p)
1203
- dims = (2,) * sz_p
1204
- if sparse is None:
1205
- sparse = issparse(A) or issparse(B)
1206
-
1207
- opts = {
1208
- "sparse": sparse,
1209
- "coo_build": sparse,
1210
- "stype": "csr" if sparse else None,
1211
- }
1212
- opab = ikron((A, B), dims, (sysa, sysb), **opts)
1213
- A = ikron((A,), dims, sysa, **opts)
1214
- B = ikron((B,), dims, sysb, **opts)
1215
-
1216
- @realify
1217
- def corr(state):
1218
- return expec(opab, state) - expec(A, state) * expec(B, state)
1219
-
1220
- return corr if precomp_func else corr(p)
1221
-
1222
-
1223
- def pauli_correlations(
1224
- p, ss=("xx", "yy", "zz"), sysa=0, sysb=1, sum_abs=False, precomp_func=False
1225
- ):
1226
- """Calculate the correlation between sites for a list of operator pairs
1227
- choisen from the pauli matrices.
1228
-
1229
- Parameters
1230
- ----------
1231
- p : ket or density operator
1232
- State to compute correlations for. Ignored if ``precomp_func=True``.
1233
- ss : tuple or str
1234
- List of pairs specifiying pauli matrices.
1235
- sysa : int, optional
1236
- Index of first site.
1237
- sysb : int, optional
1238
- Index of second site.
1239
- sum_abs : bool, optional
1240
- Whether to sum over the absolute values of each correlation
1241
- precomp_func : bool, optional
1242
- whether to return the values or a single argument
1243
- function closed over precomputed operators etc.
1244
-
1245
- Returns
1246
- -------
1247
- list of float, list of callable, float or callable
1248
- Either the value(s) of each correlation or the function(s) to compute
1249
- the correlations for an arbitrary state, depending on ``sum_abs`` and
1250
- ``precomp_func``.
1251
- """
1252
-
1253
- def gen_corr_list():
1254
- for s1, s2 in ss:
1255
- yield correlation(
1256
- p, pauli(s1), pauli(s2), sysa, sysb, precomp_func=precomp_func
1257
- )
1258
-
1259
- if sum_abs:
1260
- if precomp_func:
1261
- return lambda p: sum((abs(corr(p)) for corr in gen_corr_list()))
1262
-
1263
- return sum((abs(corr) for corr in gen_corr_list()))
1264
-
1265
- return tuple(gen_corr_list())
1266
-
1267
-
1268
- def ent_cross_matrix(
1269
- p, sz_blc=1, ent_fn=logneg, calc_self_ent=True, upscale=False
1270
- ):
1271
- """Calculate the pair-wise function ent_fn between all sites or blocks
1272
- of a state.
1273
-
1274
- Parameters
1275
- ----------
1276
- p : ket or density operator
1277
- State.
1278
- sz_blc : int
1279
- Size of the blocks to partition the state into. If the number of
1280
- individual sites is not a multiple of this then the final (smaller)
1281
- block will be ignored.
1282
- ent_fn : callable
1283
- Bipartite function, notionally entanglement
1284
- calc_self_ent : bool
1285
- Whether to calculate the function for each site
1286
- alone, purified. If the whole state is pure then this is the
1287
- entanglement with the whole remaining system.
1288
- upscale : bool, optional
1289
- Whether, if sz_blc != 1, to upscale the results so that the output
1290
- array is the same size as if it was.
1291
-
1292
- Returns
1293
- -------
1294
- 2D-array
1295
- array of pairwise ent_fn results.
1296
- """
1297
-
1298
- sz_p = infer_size(p)
1299
- dims = (2,) * sz_p
1300
- n = sz_p // sz_blc
1301
- ents = np.empty((n, n))
1302
-
1303
- ispure = isvec(p)
1304
- if ispure and sz_blc * 2 == sz_p: # pure bipartition
1305
- ent = ent_fn(p, dims=(2**sz_blc, 2**sz_blc)) / sz_blc
1306
- ents[:, :] = ent
1307
- if not calc_self_ent:
1308
- for i in range(n):
1309
- ents[i, i] = np.nan
1310
-
1311
- else:
1312
- # Range over pairwise blocks
1313
- for i in range(0, sz_p - sz_blc + 1, sz_blc):
1314
- for j in range(i, sz_p - sz_blc + 1, sz_blc):
1315
- if i == j:
1316
- if calc_self_ent:
1317
- rhoa = ptr(p, dims, [i + b for b in range(sz_blc)])
1318
- psiap = purify(rhoa)
1319
- ent = (
1320
- ent_fn(psiap, dims=(2**sz_blc, 2**sz_blc)) / sz_blc
1321
- )
1322
- else:
1323
- ent = np.nan
1324
- else:
1325
- rhoab = ptr(
1326
- p,
1327
- dims,
1328
- [i + b for b in range(sz_blc)]
1329
- + [j + b for b in range(sz_blc)],
1330
- )
1331
- ent = ent_fn(rhoab, dims=(2**sz_blc, 2**sz_blc)) / sz_blc
1332
- ents[i // sz_blc, j // sz_blc] = ent
1333
- ents[j // sz_blc, i // sz_blc] = ent
1334
-
1335
- if upscale:
1336
- up_ents = np.tile(np.nan, (sz_p, sz_p))
1337
-
1338
- for i in range(n):
1339
- for j in range(i, n):
1340
- up_ents[
1341
- i * sz_blc : (i + 1) * sz_blc,
1342
- j * sz_blc : (j + 1) * sz_blc,
1343
- ] = ents[i, j]
1344
- up_ents[
1345
- j * sz_blc : (j + 1) * sz_blc,
1346
- i * sz_blc : (i + 1) * sz_blc,
1347
- ] = ents[j, i]
1348
-
1349
- ents = up_ents
1350
-
1351
- return ents
1352
-
1353
-
1354
- def qid(
1355
- p,
1356
- dims,
1357
- inds,
1358
- precomp_func=False,
1359
- sparse_comp=True,
1360
- norm_func=norm,
1361
- power=2,
1362
- coeff=1,
1363
- ):
1364
- # Check inputs
1365
- inds = (inds,) if isinstance(inds, numbers.Number) else inds
1366
-
1367
- # Construct operators
1368
- ops_i = tuple(
1369
- tuple(ikron(pauli(s), dims, ind, sparse=sparse_comp) for s in "xyz")
1370
- for ind in inds
1371
- )
1372
-
1373
- # Define function closed over precomputed operators
1374
- def qid_func(x):
1375
- if isvec(x):
1376
- x = dop(x)
1377
- return tuple(
1378
- sum(
1379
- coeff * norm_func(dot(x, op) - dot(op, x)) ** power
1380
- for op in ops
1381
- )
1382
- for ops in ops_i
1383
- )
1384
-
1385
- return qid_func if precomp_func else qid_func(p)
1386
-
1387
-
1388
- def is_degenerate(op, tol=1e-12):
1389
- """Check if operator has any degenerate eigenvalues, determined relative
1390
- to mean spacing of all eigenvalues.
1391
-
1392
- Parameters
1393
- ----------
1394
- op : operator or 1d-array
1395
- Operator or assumed eigenvalues to check degeneracy for.
1396
- tol : float
1397
- How much closer than evenly spaced the eigenvalue gap has to be
1398
- to count as degenerate.
1399
-
1400
- Returns
1401
- -------
1402
- n_dgen : int
1403
- Number of degenerate eigenvalues.
1404
- """
1405
- op = np.asarray(op)
1406
- if op.ndim != 1:
1407
- evals = eigvalsh(op)
1408
- else:
1409
- evals = op
1410
- l_gaps = evals[1:] - evals[:-1]
1411
- l_tol = tol * (evals[-1] - evals[0]) / op.shape[0]
1412
- return np.count_nonzero(abs(l_gaps) < l_tol)
1413
-
1414
-
1415
- def is_eigenvector(x, A, tol=1e-14):
1416
- """Determines whether a vector is an eigenvector of an operator.
1417
-
1418
- Parameters
1419
- ----------
1420
- x : vector
1421
- Vector to check.
1422
- A : operator
1423
- Matrix to check.
1424
- tol : float, optional
1425
- The variance must be smaller than this value.
1426
-
1427
- Returns
1428
- -------
1429
- bool
1430
- Whether ``A @ x = l * x`` for some scalar ``l``.
1431
- """
1432
- mat_vec = dot(A, x)
1433
- E = expec(x, mat_vec)
1434
- E2 = expec(x, dot(A, mat_vec))
1435
- return abs(E**2 - E2) < tol
1436
-
1437
-
1438
- @njit
1439
- def page_entropy(sz_subsys, sz_total): # pragma: no cover
1440
- """Calculate the page entropy, i.e. expected entropy for a subsytem
1441
- of a random state in Hilbert space.
1442
-
1443
- Parameters
1444
- ----------
1445
- sz_subsys : int
1446
- Dimension of subsystem.
1447
- sz_total : int
1448
- Dimension of total system.
1449
-
1450
- Returns
1451
- -------
1452
- s : float
1453
- Entropy in bits.
1454
- """
1455
- if sz_subsys > sz_total**0.5:
1456
- sz_subsys = sz_total // sz_subsys
1457
-
1458
- n = sz_total // sz_subsys
1459
-
1460
- s = 0
1461
- for k in range(n + 1, sz_total + 1):
1462
- s += 1 / k
1463
- s -= (sz_subsys - 1) / (2 * n)
1464
-
1465
- # Normalize into bits of entropy
1466
- return s / log(2)
1467
-
1468
-
1469
- def heisenberg_energy(L):
1470
- """Get the analytic isotropic heisenberg chain ground energy for length L.
1471
- Useful for testing. Assumes the heisenberg model is defined with spin
1472
- operators not pauli matrices (overall factor of 2 smaller). Taken from [1].
1473
-
1474
- [1] Nickel, Bernie. "Scaling corrections to the ground state energy
1475
- of the spin-½ isotropic anti-ferromagnetic Heisenberg chain." Journal of
1476
- Physics Communications 1.5 (2017): 055021
1477
-
1478
- Parameters
1479
- ----------
1480
- L : int
1481
- The length of the chain.
1482
-
1483
- Returns
1484
- -------
1485
- energy : float
1486
- The ground state energy.
1487
- """
1488
- Einf = (0.5 - 2 * log(2)) * L
1489
- Efinite = pi**2 / (6 * L)
1490
- correction = 1 + 0.375 / log(L) ** 3
1491
- return (Einf - Efinite * correction) / 2