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,1167 +0,0 @@
1
- """Functions for generating quantum operators.
2
- """
3
- import math
4
- import functools
5
- import itertools
6
- import operator
7
-
8
- import numpy as np
9
- import scipy.sparse as sp
10
- from scipy.special import comb
11
-
12
- from ..utils import isiterable, concat, unique
13
- from ..core import (qarray, make_immutable, get_thread_pool,
14
- par_reduce, isreal, qu, eye, kron, ikron)
15
-
16
-
17
- # --------------------------------------------------------------------------- #
18
- # gates and other simple operators #
19
- # --------------------------------------------------------------------------- #
20
-
21
- @functools.lru_cache(maxsize=16)
22
- def spin_operator(label, S=1 / 2, **kwargs):
23
- """Generate a general spin-operator.
24
-
25
- Parameters
26
- ----------
27
- label : str
28
- The type of operator, can be one of six options:
29
-
30
- - ``{'x', 'X'}``, x-spin operator.
31
- - ``{'y', 'Y'}``, y-spin operator.
32
- - ``{'z', 'Z'}``, z-spin operator.
33
- - ``{'+', 'p'}``, Raising operator.
34
- - ``{'-', 'm'}``, Lowering operator.
35
- - ``{'i', 'I'}``, identity operator.
36
-
37
- S : float, optional
38
- The spin of particle to act on, default to spin-1/2.
39
- kwargs
40
- Passed to :func:`quimbify`.
41
-
42
- Returns
43
- -------
44
- S : immutable operator
45
- The spin operator.
46
-
47
- See Also
48
- --------
49
- pauli
50
-
51
- Examples
52
- --------
53
- >>> spin_operator('x')
54
- qarray([[0. +0.j, 0.5+0.j],
55
- [0.5+0.j, 0. +0.j]])
56
-
57
- >>> qu.spin_operator('+', S=1)
58
- qarray([[0. +0.j, 1.41421356+0.j, 0. +0.j],
59
- [0. +0.j, 0. +0.j, 1.41421356+0.j],
60
- [0. +0.j, 0. +0.j, 0. +0.j]])
61
-
62
- >>> qu.spin_operator('Y', sparse=True)
63
- <2x2 sparse matrix of type '<class 'numpy.complex128'>'
64
- with 2 stored elements in Compressed Sparse Row format>
65
-
66
- """
67
-
68
- D = int(2 * S + 1)
69
-
70
- op = np.zeros((D, D), dtype=complex)
71
- ms = np.linspace(S, -S, D)
72
-
73
- label = label.lower()
74
-
75
- if label in {'x', 'y'}:
76
- for i in range(D - 1):
77
- c = 0.5 * (S * (S + 1) - (ms[i] * ms[i + 1]))**0.5
78
- op[i, i + 1] = -1.0j * c if (label == 'y') else c
79
- op[i + 1, i] = 1.0j * c if (label == 'y') else c
80
-
81
- elif label == 'z':
82
- for i in range(D):
83
- op[i, i] = ms[i]
84
-
85
- elif label in {'+', 'p', '-', 'm'}:
86
- for i in range(D - 1):
87
- c = (S * (S + 1) - (ms[i] * ms[i + 1]))**0.5
88
- if label in {'+', 'p'}:
89
- op[i, i + 1] = c
90
- else:
91
- op[i + 1, i] = c
92
-
93
- elif label in {'i', 'I'}:
94
- np.fill_diagonal(op, 1.0)
95
-
96
- else:
97
- raise ValueError(f"Label '{label}'' not understood, should be one of "
98
- "``['X', 'Y', 'Z', '+', '-', 'I']``.")
99
-
100
- op = qu(np.real_if_close(op), **kwargs)
101
-
102
- make_immutable(op)
103
- return op
104
-
105
-
106
- @functools.lru_cache(maxsize=8)
107
- def pauli(xyz, dim=2, **kwargs):
108
- """Generates the pauli operators for dimension 2 or 3.
109
-
110
- Parameters
111
- ----------
112
- xyz : str
113
- Which spatial direction, upper or lower case from ``{'I', 'X', 'Y',
114
- 'Z'}``.
115
- dim : int, optional
116
- Dimension of spin operator (e.g. 3 for spin-1), defaults to 2 for
117
- spin half.
118
- kwargs
119
- Passed to ``quimbify``.
120
-
121
- Returns
122
- -------
123
- P : immutable operator
124
- The pauli operator.
125
-
126
- See Also
127
- --------
128
- spin_operator
129
- """
130
- xyzmap = {0: 'i', 'i': 'i', 'I': 'i',
131
- 1: 'x', 'x': 'x', 'X': 'x',
132
- 2: 'y', 'y': 'y', 'Y': 'y',
133
- 3: 'z', 'z': 'z', 'Z': 'z'}
134
- opmap = {('i', 2): lambda: eye(2, **kwargs),
135
- ('x', 2): lambda: qu([[0, 1],
136
- [1, 0]], **kwargs),
137
- ('y', 2): lambda: qu([[0, -1j],
138
- [1j, 0]], **kwargs),
139
- ('z', 2): lambda: qu([[1, 0],
140
- [0, -1]], **kwargs),
141
- ('i', 3): lambda: eye(3, **kwargs),
142
- ('x', 3): lambda: qu([[0, 1, 0],
143
- [1, 0, 1],
144
- [0, 1, 0]], **kwargs) / 2**.5,
145
- ('y', 3): lambda: qu([[0, -1j, 0],
146
- [1j, 0, -1j],
147
- [0, 1j, 0]], **kwargs) / 2**.5,
148
- ('z', 3): lambda: qu([[1, 0, 0],
149
- [0, 0, 0],
150
- [0, 0, -1]], **kwargs)}
151
- op = opmap[(xyzmap[xyz], dim)]()
152
-
153
- # Operator is cached, so make sure it cannot be modified
154
- make_immutable(op)
155
-
156
- return op
157
-
158
-
159
- @functools.lru_cache(8)
160
- def hadamard(dtype=complex, sparse=False):
161
- """The Hadamard gate.
162
- """
163
- H = qu([[1., 1.],
164
- [1., -1.]], dtype=dtype, sparse=sparse) / 2**0.5
165
- make_immutable(H)
166
- return H
167
-
168
-
169
- @functools.lru_cache(128)
170
- def phase_gate(phi, dtype=complex, sparse=False):
171
- """The generalized qubit phase-gate, which adds phase ``phi`` to the
172
- ``|1>`` state.
173
- """
174
- Rp = qu([[1., 0.],
175
- [0., np.exp(1.0j * phi)]], dtype=dtype, sparse=sparse)
176
- make_immutable(Rp)
177
- return Rp
178
-
179
-
180
- @functools.lru_cache(8)
181
- def T_gate(dtype=complex, sparse=False):
182
- """The T-gate (pi/8 gate).
183
- """
184
- return phase_gate(math.pi / 4, dtype=dtype, sparse=sparse)
185
-
186
-
187
- @functools.lru_cache(8)
188
- def S_gate(dtype=complex, sparse=False):
189
- """The S-gate (phase gate).
190
- """
191
- return phase_gate(math.pi / 2, dtype=dtype, sparse=sparse)
192
-
193
-
194
- @functools.lru_cache(128)
195
- def rotation(phi, xyz='Z', dtype=complex, sparse=False):
196
- """The single qubit rotation gate.
197
- """
198
- R = math.cos(phi / 2) * pauli('I') - 1.0j * math.sin(phi / 2) * pauli(xyz)
199
- R = qu(R, dtype=dtype, sparse=sparse)
200
- make_immutable(R)
201
- return R
202
-
203
-
204
- Rx = functools.partial(rotation, xyz='x')
205
- Ry = functools.partial(rotation, xyz='y')
206
- Rz = functools.partial(rotation, xyz='z')
207
-
208
-
209
- @functools.lru_cache(128)
210
- def U_gate(theta, phi, lamda, dtype=complex, sparse=False):
211
- r"""Arbitrary unitary single qubit gate.
212
-
213
- .. math::
214
-
215
- U_3(\theta, \phi, \lambda) =
216
- \begin{bmatrix}
217
- \cos(\theta / 2) & - e^{i \lambda} \sin(\theta / 2) \\
218
- e^{i \phi} \sin(\theta / 2) & e^{i(\lambda + \phi)}\cos(\theta / 2)
219
- \end{bmatrix}
220
-
221
- Parameters
222
- ----------
223
- theta : float
224
- Angle between 0 and pi.
225
- phi : float
226
- Angle between 0 and 2 pi.
227
- lamba : float
228
- Angle between 0 and 2 pi.
229
-
230
- Returns
231
- -------
232
- U : (2, 2) array
233
- The unitary matrix, cached.
234
- """
235
- from cmath import cos, sin, exp
236
-
237
- c2, s2 = cos(theta / 2), sin(theta / 2)
238
- U = qu(
239
- [[c2, -exp(1j * lamda) * s2],
240
- [exp(1j * phi) * s2, exp(1j * (lamda + phi)) * c2]],
241
- dtype=dtype, sparse=sparse
242
- )
243
- make_immutable(U)
244
- return U
245
-
246
-
247
- @functools.lru_cache(4)
248
- def Xsqrt(**qu_opts):
249
- r"""Rx(pi / 2).
250
-
251
- .. math::
252
-
253
- X^{\frac{1}{2}} =
254
- \frac{1}{\sqrt{2}}
255
- \begin{bmatrix}
256
- 1 & - i \\
257
- - i & 1
258
- \end{bmatrix}
259
- """
260
- return Rx(math.pi / 2, **qu_opts)
261
-
262
-
263
- @functools.lru_cache(4)
264
- def Ysqrt(**qu_opts):
265
- r"""Ry(pi / 2).
266
-
267
- .. math::
268
-
269
- Y^{\frac{1}{2}} =
270
- \frac{1}{\sqrt{2}}
271
- \begin{bmatrix}
272
- 1 & - 1 \\
273
- 1 & 1
274
- \end{bmatrix}
275
- """
276
- return Ry(math.pi / 2, **qu_opts)
277
-
278
-
279
- @functools.lru_cache(4)
280
- def Zsqrt(**qu_opts):
281
- r"""Rz(pi / 2).
282
-
283
- .. math::
284
-
285
- Z^{\frac{1}{2}} =
286
- \frac{1}{\sqrt{2}}
287
- \begin{bmatrix}
288
- 1 - i & 0 \\
289
- 0 & 1 + i
290
- \end{bmatrix}
291
- """
292
- return Rz(math.pi / 2, **qu_opts)
293
-
294
-
295
- @functools.lru_cache(4)
296
- def Wsqrt(**qu_opts):
297
- r"""R[X + Y](pi / 2).
298
-
299
- .. math::
300
-
301
- W^{\frac{1}{2}} =
302
- \frac{1}{\sqrt{2}}
303
- \begin{bmatrix}
304
- 1 & -\sqrt{i} \\
305
- \sqrt{-i} & 1
306
- \end{bmatrix}
307
- """
308
- return U_gate(math.pi / 2, -math.pi / 4, math.pi / 4, **qu_opts)
309
-
310
-
311
- @functools.lru_cache(maxsize=8)
312
- def swap(dim=2, dtype=complex, **kwargs):
313
- """The SWAP operator acting on subsystems of dimension `dim`.
314
- """
315
- S = np.identity(dim**2, dtype=dtype)
316
- S = (S.reshape([dim, dim, dim, dim])
317
- .transpose([0, 3, 1, 2])
318
- .reshape([dim**2, dim**2]))
319
- S = qu(S, dtype=dtype, **kwargs)
320
- make_immutable(S)
321
- return S
322
-
323
-
324
- @functools.lru_cache(maxsize=128)
325
- def fsim(theta, phi, dtype=complex, **kwargs):
326
- r"""The 'fermionic simulation' gate:
327
-
328
- .. math::
329
-
330
- \mathrm{fsim}(\theta, \phi) =
331
- \begin{bmatrix}
332
- 1 & 0 & 0 & 0\\
333
- 0 & \cos(\theta) & -i sin(\theta) & 0\\
334
- 0 & -i sin(\theta) & \cos(\theta) & 0\\
335
- 0 & 0 & 0 & \exp(-i \phi)
336
- \end{bmatrix}
337
-
338
- Note that ``theta`` and ``phi`` should be specified in radians and the sign
339
- convention with this gate varies. Here for example,
340
- ``fsim(- pi / 2, 0) == iswap()``.
341
- """
342
- from cmath import cos, sin, exp
343
-
344
- a = cos(theta)
345
- b = -1j * sin(theta)
346
- c = exp(-1j * phi)
347
- gate = [[1, 0, 0, 0],
348
- [0, a, b, 0],
349
- [0, b, a, 0],
350
- [0, 0, 0, c]]
351
-
352
- gate = qu(gate, dtype=dtype, **kwargs)
353
- make_immutable(gate)
354
- return gate
355
-
356
-
357
- @functools.lru_cache(maxsize=256)
358
- def fsimg(theta, zeta, chi, gamma, phi, dtype=complex, **kwargs):
359
- r"""The 'fermionic simulation' gate, with:
360
-
361
- * :math:`\theta` is the iSWAP angle
362
- * :math:`\phi` is the controlled-phase angle
363
- * :math:`\zeta, \chi, \gamma` are single-qubit phase angles.
364
-
365
- .. math::
366
- \mathrm{fsimg}(\theta, \zeta, \chi, \gamma, \phi) =
367
- \begin{bmatrix}
368
- 1 & 0 & 0 & 0\\
369
- 0 & \exp(-i(\gamma +\zeta )) \cos(\theta) &
370
- -i \exp(-i(\gamma - \chi )) sin(\theta) & 0\\
371
- 0 & -i \exp(-i(\gamma + \chi )) sin(\theta) &
372
- \exp(-i(\gamma - \zeta )) \cos(\theta) & 0\\
373
- 0 & 0 & 0 & \exp(-i (\phi +2 \gamma))
374
- \end{bmatrix}
375
-
376
- See Equation 18 of https://arxiv.org/abs/2010.07965. Note that ``theta``,
377
- ``phi``, ``zeta``, ``chi``, ``gamma`` should be specified in radians and
378
- the sign convention with this gate varies. Here for example,
379
- ``fsimg(- pi / 2, 0, 0, 0,0) == iswap()``.
380
- """
381
- from cmath import cos, sin, exp
382
-
383
- a1 = exp(-1j * (gamma + zeta)) * cos(theta)
384
- a2 = exp(-1j * (gamma - zeta)) * cos(theta)
385
-
386
- b1 = -1j * exp(-1j * (gamma - chi)) * sin(theta)
387
- b2 = -1j * exp(-1j * (gamma + chi)) * sin(theta)
388
-
389
- c = exp(-1j * (phi + 2 * gamma))
390
-
391
- gate = [[1, 0, 0, 0],
392
- [0, a1, b1, 0],
393
- [0, b2, a2, 0],
394
- [0, 0, 0, c]]
395
- gate = qu(gate, dtype=dtype, **kwargs)
396
- make_immutable(gate)
397
- return gate
398
-
399
-
400
- @functools.lru_cache(maxsize=4)
401
- def iswap(dtype=complex, **kwargs):
402
- iswap = qu([[1., 0., 0., 0.],
403
- [0., 0., 1j, 0.],
404
- [0., 1j, 0., 0.],
405
- [0., 0., 0., 1.]], dtype=dtype, **kwargs)
406
- make_immutable(iswap)
407
- return iswap
408
-
409
-
410
- def ncontrolled_gate(ncontrol, gate, dtype=complex, sparse=False):
411
- """Build an n-qubit controlled gate. The control qubits are the
412
- first ``ncontrol`` qubits.
413
-
414
- Parameters
415
- ----------
416
- ncontrol : int
417
- The number of control qubits.
418
- gate : array_like
419
- The gate to apply to the controlled qubit(s).
420
- dtype : str
421
- The data type of the returned matrix.
422
- sparse : bool
423
- Whether to return a sparse matrix.
424
-
425
- Returns
426
- -------
427
- C : qarray
428
- The n-qubit controlled gate.
429
- """
430
- dG = gate.shape[0]
431
- d = 2**ncontrol * dG
432
- # build gate dense and dtype='complex128'
433
- op = np.identity(d, dtype='complex128')
434
- op[-dG:, -dG:] = gate
435
- # then convert to desired dtype and format
436
- op = qu(op, dtype=dtype, sparse=sparse)
437
- return op
438
-
439
-
440
- @functools.lru_cache(maxsize=16)
441
- def controlled(s, dtype=complex, sparse=False):
442
- """Construct a controlled pauli gate for two qubits.
443
-
444
- Parameters
445
- ----------
446
- s : str
447
- Which pauli to use, including 'not' aliased to 'x'.
448
- sparse : bool, optional
449
- Whether to construct a sparse operator.
450
-
451
- Returns
452
- -------
453
- C : qarray
454
- The controlled two-qubit gate operator.
455
- """
456
- # alias not and NOT to x
457
- s = {'NOT': 'x', 'not': 'x'}.get(s, s)
458
- gate = pauli(s)
459
- op = ncontrolled_gate(1, gate, dtype=dtype, sparse=sparse)
460
- make_immutable(op)
461
- return op
462
-
463
-
464
- @functools.lru_cache(8)
465
- def CNOT(dtype=complex, sparse=False):
466
- """The controlled-not gate.
467
- """
468
- return controlled('not', dtype=dtype, sparse=sparse)
469
-
470
-
471
- @functools.lru_cache(8)
472
- def cX(dtype=complex, sparse=False):
473
- """The controlled-X gate.
474
- """
475
- return controlled('not', dtype=dtype, sparse=sparse)
476
-
477
-
478
- @functools.lru_cache(8)
479
- def cY(dtype=complex, sparse=False):
480
- """The controlled-Y gate.
481
- """
482
- return controlled('Y', dtype=dtype, sparse=sparse)
483
-
484
-
485
- @functools.lru_cache(8)
486
- def cZ(dtype=complex, sparse=False):
487
- """The controlled-Z gate.
488
- """
489
- return controlled('Z', dtype=dtype, sparse=sparse)
490
-
491
-
492
- @functools.lru_cache(8)
493
- def ccX(dtype=complex, sparse=False):
494
- """The double controlled X gate, or Toffoli gate.
495
- """
496
- op = ncontrolled_gate(2, pauli('X'), dtype=dtype, sparse=sparse)
497
- make_immutable(op)
498
- return op
499
-
500
-
501
- @functools.lru_cache(8)
502
- def ccY(dtype=complex, sparse=False):
503
- """The double controlled Y gate.
504
- """
505
- op = ncontrolled_gate(2, pauli('Y'), dtype=dtype, sparse=sparse)
506
- make_immutable(op)
507
- return op
508
-
509
-
510
- @functools.lru_cache(8)
511
- def ccZ(dtype=complex, sparse=False):
512
- """The double controlled Z gate.
513
- """
514
- op = ncontrolled_gate(2, pauli('Z'), dtype=dtype, sparse=sparse)
515
- make_immutable(op)
516
- return op
517
-
518
-
519
- @functools.lru_cache(8)
520
- def controlled_swap(dtype=complex, sparse=False):
521
- """The controlled swap or Fredkin gate. The control qubit is the first
522
- qubit, if in state |1> a swap is performed on the last two qubits.
523
- """
524
- op = ncontrolled_gate(1, swap(), dtype=dtype, sparse=sparse)
525
- make_immutable(op)
526
- return op
527
-
528
-
529
- cswap = controlled_swap
530
- fredkin = controlled_swap
531
- toffoli = ccX
532
-
533
-
534
- # --------------------------------------------------------------------------- #
535
- # Hamiltonians #
536
- # --------------------------------------------------------------------------- #
537
-
538
-
539
- def hamiltonian_builder(fn):
540
- """Wrap a function to perform some generic postprocessing and take the
541
- kwargs ``stype`` and ``sparse``. This assumes the core function always
542
- builds the hamiltonian in sparse form. The wrapper then:
543
-
544
- 1. Checks if the operator is real and, if so, discards imaginary part if no
545
- explicity `dtype` was given
546
- 2. Converts the operator to dense or the correct sparse form
547
- 3. Makes the operator immutable so it can be safely cached
548
- """
549
-
550
- @functools.wraps(fn)
551
- def ham_fn(*args, stype='csr', sparse=False, **kwargs):
552
- H = fn(*args, **kwargs)
553
-
554
- if kwargs.get('dtype', None) is None and isreal(H):
555
- H = H.real
556
-
557
- if not sparse:
558
- H = qarray(H.toarray())
559
- elif H.format != stype:
560
- H = H.asformat(stype)
561
-
562
- make_immutable(H)
563
-
564
- return H
565
-
566
- return ham_fn
567
-
568
-
569
- @functools.lru_cache(maxsize=8)
570
- @hamiltonian_builder
571
- def ham_heis(n, j=1.0, b=0.0, cyclic=False,
572
- parallel=False, nthreads=None, ownership=None):
573
- """Constructs the nearest neighbour 1d heisenberg spin-1/2 hamiltonian.
574
-
575
- Parameters
576
- ----------
577
- n : int
578
- Number of spins.
579
- j : float or tuple(float, float, float), optional
580
- Coupling constant(s), with convention that positive =
581
- antiferromagnetic. Can supply scalar for isotropic coupling or
582
- vector ``(jx, jy, jz)``.
583
- b : float or tuple(float, float, float), optional
584
- Magnetic field, defaults to z-direction only if tuple not given.
585
- cyclic : bool, optional
586
- Whether to couple the first and last spins.
587
- sparse : bool, optional
588
- Whether to return the hamiltonian in sparse form.
589
- stype : str, optional
590
- What format of sparse operator to return if ``sparse``.
591
- parallel : bool, optional
592
- Whether to build the operator in parallel. By default will do this
593
- for n > 16.
594
- nthreads : int optional
595
- How mny threads to use in parallel to build the operator.
596
- ownership : (int, int), optional
597
- If given, which range of rows to generate.
598
- kwargs
599
- Supplied to :func:`~quimb.core.quimbify`.
600
-
601
- Returns
602
- -------
603
- H : immutable operator
604
- The Hamiltonian.
605
- """
606
- dims = (2,) * n
607
- try:
608
- jx, jy, jz = j
609
- except TypeError:
610
- jx = jy = jz = j
611
-
612
- try:
613
- bx, by, bz = b
614
- except TypeError:
615
- bz = b
616
- bx = by = 0.0
617
-
618
- parallel = (n > 16) if parallel is None else parallel
619
-
620
- op_kws = {'sparse': True, 'stype': 'coo'}
621
- ikron_kws = {'sparse': True, 'stype': 'coo',
622
- 'coo_build': True, 'ownership': ownership}
623
-
624
- # The basic operator (interaction and single b-field) that can be repeated.
625
- two_site_term = sum(
626
- j * kron(spin_operator(s, **op_kws), spin_operator(s, **op_kws))
627
- for j, s in zip((jx, jy, jz), 'xyz')
628
- ) - sum(
629
- b * kron(spin_operator(s, **op_kws), eye(2, **op_kws))
630
- for b, s in zip((bx, by, bz), 'xyz') if b != 0.0
631
- )
632
-
633
- single_site_b = sum(-b * spin_operator(s, **op_kws)
634
- for b, s in zip((bx, by, bz), 'xyz') if b != 0.0)
635
-
636
- def gen_term(i):
637
- # special case: the last b term needs to be added manually
638
- if i == -1:
639
- return ikron(single_site_b, dims, n - 1, **ikron_kws)
640
-
641
- # special case: the interaction between first and last spins if cyclic
642
- if i == n - 1:
643
- return sum(
644
- j * ikron(spin_operator(s, **op_kws),
645
- dims, [0, n - 1], **ikron_kws)
646
- for j, s in zip((jx, jy, jz), 'xyz') if j != 0.0)
647
-
648
- # General term, on-site b-field plus interaction with next site
649
- return ikron(two_site_term, dims, [i, i + 1], **ikron_kws)
650
-
651
- terms_needed = range(0 if not any((bx, by, bz)) else -1,
652
- n if cyclic else n - 1)
653
-
654
- if parallel:
655
- pool = get_thread_pool(nthreads)
656
- ham = par_reduce(operator.add, pool.map(gen_term, terms_needed))
657
- else:
658
- ham = sum(map(gen_term, terms_needed))
659
-
660
- return ham
661
-
662
-
663
- def ham_ising(n, jz=1.0, bx=1.0, **ham_opts):
664
- """Generate the quantum transverse field ising model hamiltonian. This is a
665
- simple alias for :func:`~quimb.gen.operators.ham_heis` with Z-interactions
666
- and an X-field.
667
- """
668
- return ham_heis(n, j=(0, 0, jz), b=(bx, 0, 0), **ham_opts)
669
-
670
-
671
- def ham_XY(n, jxy, bz, **ham_opts):
672
- """Generate the quantum transverse field XY model hamiltonian. This is a
673
- simple alias for :func:`~quimb.gen.operators.ham_heis` with
674
- X- and Y-interactions and a Z-field.
675
- """
676
- return ham_heis(n, j=(jxy, jxy, 0), b=(0, 0, bz), **ham_opts)
677
-
678
-
679
- def ham_XXZ(n, delta, jxy=1.0, **ham_opts):
680
- """Generate the XXZ-model hamiltonian. This is a
681
- simple alias for :func:`~quimb.gen.operators.ham_heis` with matched
682
- X- and Y-interactions and ``delta`` Z coupling.
683
- """
684
- return ham_heis(n, j=(jxy, jxy, delta), b=0, **ham_opts)
685
-
686
-
687
- @functools.lru_cache(maxsize=8)
688
- @hamiltonian_builder
689
- def ham_j1j2(n, j1=1.0, j2=0.5, bz=0.0, cyclic=False, ownership=None):
690
- """Generate the j1-j2 hamiltonian, i.e. next nearest neighbour
691
- interactions.
692
-
693
- Parameters
694
- ----------
695
- n : int
696
- Number of spins.
697
- j1 : float, optional
698
- Nearest neighbour coupling strength.
699
- j2 : float, optional
700
- Next nearest neighbour coupling strength.
701
- bz : float, optional
702
- B-field strength in z-direction.
703
- cyclic : bool, optional
704
- Cyclic boundary conditions.
705
- sparse : bool, optional
706
- Return hamiltonian as sparse-csr operator.
707
- ownership : (int, int), optional
708
- If given, which range of rows to generate.
709
- kwargs
710
- Supplied to :func:`~quimb.core.quimbify`.
711
-
712
- Returns
713
- -------
714
- H : immutable operator
715
- The Hamiltonian.
716
- """
717
- dims = (2,) * n
718
-
719
- op_kws = {'sparse': True, 'stype': 'coo'}
720
- ikron_kws = {'sparse': True, 'stype': 'coo',
721
- 'coo_build': True, 'ownership': ownership}
722
-
723
- sxyz = [spin_operator(i, **op_kws) for i in 'xyz']
724
-
725
- coosj1 = np.array([(i, i + 1) for i in range(n)])
726
- coosj2 = np.array([(i, i + 2) for i in range(n)])
727
- if cyclic:
728
- coosj1, coosj2 = coosj1 % n, coosj2 % n
729
- else:
730
- coosj1 = coosj1[np.all(coosj1 < n, axis=1)]
731
- coosj2 = coosj2[np.all(coosj2 < n, axis=1)]
732
-
733
- def j1_terms():
734
- for coo in coosj1:
735
- if abs(coo[1] - coo[0]) == 1: # can sum then tensor (faster)
736
- yield ikron(sum(op & op for op in sxyz),
737
- dims, coo, **ikron_kws)
738
- else: # tensor then sum (slower)
739
- yield sum(ikron(op, dims, coo, **ikron_kws) for op in sxyz)
740
-
741
- def j2_terms():
742
- for coo in coosj2:
743
- if abs(coo[1] - coo[0]) == 2: # can add then tensor (faster)
744
- yield ikron(sum(op & eye(2, **op_kws) & op for op in sxyz),
745
- dims, coo, **ikron_kws)
746
- else:
747
- yield sum(ikron(op, dims, coo, **ikron_kws) for op in sxyz)
748
-
749
- ham = j1 * sum(j1_terms()) + j2 * sum(j2_terms())
750
-
751
- if bz != 0:
752
- gen_bz = (ikron([sxyz[2]], dims, i, **ikron_kws) for i in range(n))
753
- ham += bz * sum(gen_bz)
754
-
755
- return ham
756
-
757
-
758
- def _gen_mbl_random_factors(n, dh, dh_dim, dh_dist, seed=None, beta=None):
759
- # sort out a vector of noise strengths -> e.g. (0, 0, 1) for z-noise only
760
- if isinstance(dh, (tuple, list)):
761
- dhds = dh
762
- else:
763
- dh_dim = {0: '', 1: 'z', 2: 'xy', 3: 'xyz'}.get(dh_dim, dh_dim)
764
- dhds = tuple((dh if d in dh_dim else 0) for d in 'xyz')
765
-
766
- if seed is not None:
767
- np.random.seed(seed)
768
-
769
- # sort out the noise distribution
770
- if dh_dist in {'g', 'gauss', 'gaussian', 'normal'}:
771
- rs = np.random.randn(3, n)
772
-
773
- elif dh_dist in {'s', 'flat', 'square', 'uniform', 'box'}:
774
- rs = 2.0 * np.random.rand(3, n) - 1.0
775
-
776
- elif dh_dist in {'qp', 'quasiperiodic', 'qr', 'quasirandom'}:
777
- if dh_dim != 'z':
778
- raise ValueError("dh_dim should be 1 or 'z' for dh_dist='qp'.")
779
-
780
- if beta is None:
781
- beta = (5**0.5 - 1) / 2
782
-
783
- # the random phase
784
- delta = 2 * np.pi * np.random.rand()
785
-
786
- # make sure get 3 by n different strengths
787
- inds = np.broadcast_to(range(n), (3, n))
788
-
789
- rs = np.cos(2 * np.pi * beta * inds + delta)
790
-
791
- return dhds, rs
792
-
793
-
794
- @hamiltonian_builder
795
- def ham_mbl(n, dh, j=1.0, bz=0.0, cyclic=False,
796
- seed=None, dh_dist="s", dh_dim=1, beta=None, ownership=None):
797
- """ Constructs a heisenberg hamiltonian with isotropic coupling and
798
- random fields acting on each spin - the many-body localized (MBL)
799
- spin hamiltonian.
800
-
801
- Parameters
802
- ----------
803
- n : int
804
- Number of spins.
805
- dh : float or (float, float, float)
806
- Strength of random fields (stdev of gaussian distribution), can be
807
- scalar (isotropic noise) or 3-vector for (x, y, z) directions.
808
- j : float or (float, float, float), optional
809
- Coupling strength, can be scalar (isotropic) or 3-vector.
810
- bz : float, optional
811
- Global magnetic field (in z-direction).
812
- cyclic : bool, optional
813
- Whether to use periodic boundary conditions.
814
- seed : int, optional
815
- Number to seed random number generator with.
816
- dh_dist : {'g', 's', 'qr'}, optional
817
- Type of random distribution for the noise:
818
-
819
- - "s": square, with bounds ``(-dh, dh)``
820
- - "g": gaussian, with standard deviation ``dh``
821
- - "qp": quasi periodic, with amplitude ``dh`` and
822
- 'wavenumber' ``beta`` so that the field at site ``i`` is
823
- ``dh * cos(2 * pi * beta * i + delta)`` with ``delta`` a random
824
- offset between ``(0, 2 * pi)``, possibly seeded by ``seed``.
825
-
826
- dh_dim : {1, 2, 3} or str, optional
827
- The number of dimensions the noise acts in, or string
828
- specifier like ``'yz'``.
829
- beta : float, optional
830
- The wave number if ``dh_dist='qr'``, defaults to the golden
831
- ratio``(5**0.5 - 1) / 2``.
832
- sparse : bool, optional
833
- Whether to construct the hamiltonian in sparse form.
834
- stype : {'csr', 'csc', 'coo'}, optional
835
- The sparse format.
836
- ownership : (int, int), optional
837
- If given, which range of rows to generate.
838
- kwargs
839
- Supplied to :func:`~quimb.core.quimbify`.
840
-
841
- Returns
842
- -------
843
- H : operator
844
- The MBL hamiltonian for spin-1/2.
845
-
846
- See Also
847
- --------
848
- MPO_ham_mbl
849
- """
850
- dhds, rs = _gen_mbl_random_factors(n, dh, dh_dim, dh_dist, seed, beta)
851
-
852
- # the base hamiltonian ('csr' is most efficient format to add with)
853
- ham = ham_heis(n=n, j=j, b=bz, cyclic=cyclic,
854
- sparse=True, stype='csr', ownership=ownership)
855
-
856
- op_kws = {'sparse': True, 'stype': 'coo'}
857
- ikron_kws = {'sparse': True, 'stype': 'coo',
858
- 'coo_build': True, 'ownership': ownership}
859
-
860
- def dh_terms():
861
- for i in range(n):
862
- # dhd - the total strength in direction x, y, or z
863
- # r - the random strength in direction x, y, or z for site i
864
- hdh = sum(dhd * r * spin_operator(s, **op_kws)
865
- for dhd, r, s in zip(dhds, rs[:, i], 'xyz'))
866
- yield ikron(hdh, (2,) * n, i, **ikron_kws)
867
-
868
- ham = ham + sum(dh_terms())
869
-
870
- return ham
871
-
872
-
873
- @hamiltonian_builder
874
- def ham_heis_2D(n, m, j=1.0, bz=0.0, cyclic=False,
875
- parallel=False, ownership=None):
876
- r"""Construct the 2D spin-1/2 heisenberg model hamiltonian:
877
-
878
- .. math::
879
-
880
- \hat{H} = \sum_{<i, j>}
881
- J_X S^X_i S^X_j +
882
- J_Y S^Y_i S^Y_j +
883
- J_Z S^Z_i S^Z_j
884
-
885
- where the sum runs over pairs :math:`<i,j>` on a 2D square lattice.
886
-
887
- Parameters
888
- ----------
889
- n : int
890
- The number of rows.
891
- m : int
892
- The number of columns.
893
- j : float or (float, float, float), optional
894
- The coupling strength(s). Isotropic if scalar else if
895
- vector ``(Jx, Jy, Jz) = j``.
896
- bz : float, optional
897
- The z direction magnetic field.
898
- cyclic : bool, optional
899
- Whether to use periodic boundary conditions.
900
- sparse : bool, optional
901
- Whether to construct the hamiltonian in sparse form.
902
- stype : {'csr', 'csc', 'coo'}, optional
903
- The sparse format.
904
- parallel : bool, optional
905
- Construct the hamiltonian in parallel. Faster but might use more
906
- memory.
907
- ownership : (int, int), optional
908
- If given, which range of rows to generate.
909
- kwargs
910
- Supplied to :func:`~quimb.core.quimbify`.
911
-
912
- Returns
913
- -------
914
- H : operator
915
- The hamiltonian.
916
- """
917
-
918
- # parse interaction strengths
919
- try:
920
- jx, jy, jz = j
921
- except (TypeError, ValueError):
922
- jx = jy = jz = j
923
-
924
- js = {s: js for s, js in zip("xyz", [jx, jy, jz]) if js != 0.0}
925
-
926
- dims = [[2] * m] * n # shape (n, m)
927
-
928
- sites = tuple(itertools.product(range(n), range(m)))
929
-
930
- # generate neighbouring pair coordinates
931
- def gen_pairs():
932
- for i, j in sites:
933
- above, right = (i + 1) % n, (j + 1) % m
934
- # ignore wraparound coordinates if not cyclic
935
- if cyclic or above != 0:
936
- yield ((i, j), (above, j))
937
- if cyclic or right != 0:
938
- yield ((i, j), (i, right))
939
-
940
- # generate all pairs of coordinates and directions
941
- pairs_ss = tuple(itertools.product(gen_pairs(), js))
942
-
943
- # build the hamiltonian in sparse 'coo' format always for efficiency
944
- op_kws = {'sparse': True, 'stype': 'coo'}
945
- ikron_kws = {'sparse': True, 'stype': 'coo',
946
- 'coo_build': True, 'ownership': ownership}
947
-
948
- # generate XX, YY and ZZ interaction from
949
- # e.g. arg ([(3, 4), (3, 5)], 'z')
950
- def interactions(pair_s):
951
- pair, s = pair_s
952
- Sxyz = spin_operator(s, **op_kws)
953
- return ikron([js[s] * Sxyz, Sxyz], dims, inds=pair, **ikron_kws)
954
-
955
- # generate Z field
956
- def fields(site):
957
- Sz = spin_operator('z', **op_kws)
958
- return ikron(bz * Sz, dims, inds=[site], **ikron_kws)
959
-
960
- if not parallel:
961
- # combine all terms
962
- all_terms = itertools.chain(
963
- map(interactions, pairs_ss),
964
- map(fields, sites) if bz != 0.0 else ())
965
- H = functools.reduce(operator.add, all_terms)
966
- else:
967
- pool = get_thread_pool()
968
- all_terms = itertools.chain(
969
- pool.map(interactions, pairs_ss),
970
- pool.map(fields, sites) if bz != 0.0 else ())
971
- H = par_reduce(operator.add, all_terms)
972
-
973
- return H
974
-
975
-
976
- def uniq_perms(xs):
977
- """Generate all the unique permutations of sequence ``xs``.
978
-
979
- Examples
980
- --------
981
- >>> list(uniq_perms('0011'))
982
- [('0', '0', '1', '1'),
983
- ('0', '1', '0', '1'),
984
- ('0', '1', '1', '0'),
985
- ('1', '0', '0', '1'),
986
- ('1', '0', '1', '0'),
987
- ('1', '1', '0', '0')]
988
- """
989
- if len(xs) == 1:
990
- yield (xs[0],)
991
- else:
992
- uniq_xs = unique(xs)
993
- for first_x in uniq_xs:
994
- rem_xs = list(xs)
995
- rem_xs.remove(first_x)
996
- for sub_perm in uniq_perms(rem_xs):
997
- yield (first_x,) + sub_perm
998
-
999
-
1000
- @functools.lru_cache(maxsize=8)
1001
- def zspin_projector(n, sz=0, stype="csr", dtype=float):
1002
- """Construct the projector onto spin-z subpspaces.
1003
-
1004
- Parameters
1005
- ----------
1006
- n : int
1007
- Total size of spin system.
1008
- sz : float or sequence of floats
1009
- Spin-z value(s) subspace(s) to find projector for.
1010
- stype : str
1011
- Sparse format of the output operator.
1012
- dtype : {float, complex}, optional
1013
- The data type of the operator to generate.
1014
-
1015
- Returns
1016
- -------
1017
- prj : immutable sparse operator, shape (2**n, D)
1018
- The (non-square) projector onto the specified subspace(s). The subspace
1019
- size ``D`` is given by ``n choose (n / 2 + s)`` for each ``s``
1020
- specified in ``sz``.
1021
-
1022
- Examples
1023
- --------
1024
- >>> zspin_projector(n=2, sz=0).toarray()
1025
- array([[0., 0.],
1026
- [1., 0.],
1027
- [0., 1.],
1028
- [0., 0.]]
1029
-
1030
- Project a 9-spin Heisenberg-Hamiltonian into its spin-1/2 subspace:
1031
-
1032
- >>> H = ham_heis(9, sparse=True)
1033
- >>> H.shape
1034
- (512, 512)
1035
-
1036
- >>> P = zspin_projector(n=9, sz=1 / 2)
1037
- >>> H0 = P.T @ H @ P
1038
- >>> H0.shape
1039
- (126, 126)
1040
- """
1041
- if not isiterable(sz):
1042
- sz = (sz,)
1043
-
1044
- p = 0
1045
- all_perms = []
1046
-
1047
- for s in sz:
1048
- # Number of 'up' spins
1049
- k = n / 2 + s
1050
- if not k.is_integer():
1051
- raise ValueError(f"{s} is not a valid spin half subspace for {n} "
1052
- "spins.")
1053
- k = int(round(k))
1054
- # Size of subspace
1055
- p += comb(n, k, exact=True)
1056
- # Find all computational basis states with correct number of 0s and 1s
1057
- base_perm = '0' * (n - k) + '1' * k
1058
- all_perms += [uniq_perms(base_perm)]
1059
-
1060
- # Coordinates
1061
- cis = tuple(range(p)) # arbitrary basis
1062
- cjs = tuple(int("".join(perm), 2) for perm in concat(all_perms))
1063
-
1064
- # Construct matrix which projects only on to these basis states
1065
- prj = sp.coo_matrix((np.ones(p, dtype=dtype), (cjs, cis)),
1066
- shape=(2**n, p), dtype=dtype)
1067
- prj = qu(prj, stype=stype, dtype=dtype)
1068
- make_immutable(prj)
1069
- return prj
1070
-
1071
-
1072
- @functools.lru_cache(8)
1073
- def create(n=2, **qu_opts):
1074
- """The creation operator acting on an n-level system.
1075
- """
1076
- data = np.zeros((n, n))
1077
-
1078
- for i in range(n):
1079
- data[i, i - 1] = i ** 0.5
1080
-
1081
- ap = qu(data, **qu_opts)
1082
- make_immutable(ap)
1083
- return ap
1084
-
1085
-
1086
- @functools.lru_cache(8)
1087
- def destroy(n=2, **qu_opts):
1088
- """The annihilation operator acting on an n-level system.
1089
- """
1090
- am = create(n, **qu_opts).T.copy()
1091
- make_immutable(am)
1092
- return am
1093
-
1094
-
1095
- @functools.lru_cache(8)
1096
- def num(n, **qu_opts):
1097
- """The number operator acting on an n-level system.
1098
- """
1099
- ap, am = create(n, **qu_opts), destroy(n, **qu_opts)
1100
- an = qu(ap @ am, **qu_opts)
1101
- make_immutable(an)
1102
- return an
1103
-
1104
-
1105
- @functools.lru_cache(maxsize=8)
1106
- @hamiltonian_builder
1107
- def ham_hubbard_hardcore(n, t=0.5, V=1., mu=1., cyclic=False,
1108
- parallel=False, ownership=None):
1109
- """Generate the spinless fermion hopping hamiltonian.
1110
-
1111
- Parameters
1112
- ----------
1113
- n : int
1114
- The number of sites.
1115
- t : float, optional
1116
- The hopping energy.
1117
- V : float, optional
1118
- The interaction energy.
1119
- mu : float, optional
1120
- The chemical potential - defaults to half-filling.
1121
- cyclic : bool, optional
1122
- Whether to use periodic boundary conditions.
1123
- parallel : bool, optional
1124
- Construct the hamiltonian in parallel. Faster but might use more
1125
- memory.
1126
- ownership : (int, int), optional
1127
- If given, which range of rows to generate.
1128
- kwargs
1129
- Supplied to :func:`~quimb.core.quimbify`.
1130
-
1131
- Returns
1132
- -------
1133
- H : operator
1134
- The hamiltonian.
1135
- """
1136
-
1137
- op_kws = {'sparse': True, 'stype': 'coo'}
1138
- ikron_kws = {'sparse': True, 'stype': 'csr',
1139
- 'coo_build': True, 'ownership': ownership}
1140
-
1141
- cdag, c, cnum = (f(2, **op_kws) for f in (create, destroy, num))
1142
- neighbor_term = t * ((cdag & c) + (c & cdag)) + V * (cnum & cnum)
1143
-
1144
- dims = [2] * n
1145
-
1146
- def terms():
1147
- # interacting terms
1148
- for i, j in [(i, i + 1) for i in range(n - 1)]:
1149
- yield ikron(neighbor_term, dims, (i, j), **ikron_kws)
1150
-
1151
- if cyclic:
1152
- # can't sum terms and kron later since identity in middle
1153
- yield ikron([t * cdag, c], dims, (0, n - 1), **ikron_kws)
1154
- yield ikron([t * c, cdag], dims, (0, n - 1), **ikron_kws)
1155
- yield ikron([V * cnum, cnum], dims, (0, n - 1), **ikron_kws)
1156
-
1157
- # single site terms
1158
- for i in range(n):
1159
- yield ikron(-mu * cnum, dims, i, **ikron_kws)
1160
-
1161
- if parallel is None:
1162
- parallel = (n >= 14)
1163
-
1164
- if parallel:
1165
- return par_reduce(operator.add, terms())
1166
-
1167
- return functools.reduce(operator.add, terms())