tensorcircuit-nightly 1.3.0.dev20250728__py3-none-any.whl → 1.4.0.dev20251103__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.

Potentially problematic release.


This version of tensorcircuit-nightly might be problematic. Click here for more details.

Files changed (72) hide show
  1. tensorcircuit/__init__.py +5 -1
  2. tensorcircuit/abstractcircuit.py +4 -0
  3. tensorcircuit/analogcircuit.py +413 -0
  4. tensorcircuit/applications/layers.py +1 -1
  5. tensorcircuit/applications/van.py +1 -1
  6. tensorcircuit/backends/abstract_backend.py +312 -5
  7. tensorcircuit/backends/cupy_backend.py +3 -1
  8. tensorcircuit/backends/jax_backend.py +92 -3
  9. tensorcircuit/backends/jax_ops.py +108 -0
  10. tensorcircuit/backends/numpy_backend.py +49 -3
  11. tensorcircuit/backends/pytorch_backend.py +92 -3
  12. tensorcircuit/backends/tensorflow_backend.py +102 -3
  13. tensorcircuit/basecircuit.py +123 -82
  14. tensorcircuit/circuit.py +67 -57
  15. tensorcircuit/cloud/local.py +1 -1
  16. tensorcircuit/cloud/quafu_provider.py +1 -1
  17. tensorcircuit/cloud/tencent.py +1 -1
  18. tensorcircuit/compiler/simple_compiler.py +2 -2
  19. tensorcircuit/cons.py +1 -0
  20. tensorcircuit/densitymatrix.py +16 -11
  21. tensorcircuit/experimental.py +7 -152
  22. tensorcircuit/fgs.py +5 -6
  23. tensorcircuit/gates.py +66 -22
  24. tensorcircuit/keras.py +3 -3
  25. tensorcircuit/mpscircuit.py +109 -61
  26. tensorcircuit/quantum.py +697 -133
  27. tensorcircuit/quditcircuit.py +733 -0
  28. tensorcircuit/quditgates.py +618 -0
  29. tensorcircuit/results/counts.py +45 -31
  30. tensorcircuit/shadows.py +1 -1
  31. tensorcircuit/simplify.py +3 -1
  32. tensorcircuit/stabilizercircuit.py +4 -2
  33. tensorcircuit/templates/blocks.py +2 -2
  34. tensorcircuit/templates/hamiltonians.py +29 -8
  35. tensorcircuit/templates/lattice.py +676 -335
  36. tensorcircuit/timeevol.py +896 -0
  37. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/METADATA +50 -25
  38. tensorcircuit_nightly-1.4.0.dev20251103.dist-info/RECORD +96 -0
  39. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/top_level.txt +0 -1
  40. tensorcircuit_nightly-1.3.0.dev20250728.dist-info/RECORD +0 -122
  41. tests/__init__.py +0 -0
  42. tests/conftest.py +0 -67
  43. tests/test_backends.py +0 -1035
  44. tests/test_calibrating.py +0 -149
  45. tests/test_channels.py +0 -409
  46. tests/test_circuit.py +0 -1713
  47. tests/test_cloud.py +0 -219
  48. tests/test_compiler.py +0 -147
  49. tests/test_dmcircuit.py +0 -555
  50. tests/test_ensemble.py +0 -72
  51. tests/test_fgs.py +0 -318
  52. tests/test_gates.py +0 -156
  53. tests/test_hamiltonians.py +0 -159
  54. tests/test_interfaces.py +0 -557
  55. tests/test_keras.py +0 -160
  56. tests/test_lattice.py +0 -1666
  57. tests/test_miscs.py +0 -334
  58. tests/test_mpscircuit.py +0 -341
  59. tests/test_noisemodel.py +0 -156
  60. tests/test_qaoa.py +0 -86
  61. tests/test_qem.py +0 -152
  62. tests/test_quantum.py +0 -549
  63. tests/test_quantum_attr.py +0 -42
  64. tests/test_results.py +0 -379
  65. tests/test_shadows.py +0 -160
  66. tests/test_simplify.py +0 -46
  67. tests/test_stabilizer.py +0 -226
  68. tests/test_templates.py +0 -218
  69. tests/test_torchnn.py +0 -99
  70. tests/test_van.py +0 -102
  71. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/WHEEL +0 -0
  72. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,618 @@
1
+ r"""
2
+ Single-qudit and two-qudit gates and their matrix representations.
3
+
4
+ This module implements gates for **qudits** (d-level systems), providing d-dimensional
5
+ analogues of familiar qubit gates as well as qudit-specific primitives.
6
+
7
+ **Registry**
8
+ Single-qudit builders: ``I``, ``X``, ``Z``, ``H``, ``RX``, ``RY``, ``RZ``, ``U8``.
9
+ Two-qudit builders: ``RXX``, ``RZZ``, ``CPHASE``, ``CSUM``.
10
+
11
+ These names are used by higher-level APIs (e.g. :class:`tensorcircuit.quditcircuit.QuditCircuit`).
12
+ """
13
+
14
+ from typing import Any, Optional, Tuple
15
+
16
+ import numpy as np
17
+
18
+ from .cons import backend, dtypestr
19
+ from .gates import num_to_tensor
20
+
21
+ Tensor = Any
22
+
23
+ SINGLE_BUILDERS = {
24
+ "I": (("none",), lambda d, omega, **kw: i_matrix_func(d)),
25
+ "X": (("none",), lambda d, omega, **kw: x_matrix_func(d)),
26
+ "Z": (("none",), lambda d, omega, **kw: z_matrix_func(d, omega)),
27
+ "H": (("none",), lambda d, omega, **kw: h_matrix_func(d, omega)),
28
+ "RX": (
29
+ ("theta", "j", "k"),
30
+ lambda d, omega, **kw: rx_matrix_func(d, kw["theta"], kw["j"], kw["k"]),
31
+ ),
32
+ "RY": (
33
+ ("theta", "j", "k"),
34
+ lambda d, omega, **kw: ry_matrix_func(d, kw["theta"], kw["j"], kw["k"]),
35
+ ),
36
+ "RZ": (
37
+ ("theta", "j"),
38
+ lambda d, omega, **kw: rz_matrix_func(d, kw["theta"], kw["j"]),
39
+ ),
40
+ "U8": (
41
+ ("gamma", "z", "eps"),
42
+ lambda d, omega, **kw: u8_matrix_func(
43
+ d, kw["gamma"], kw["z"], kw["eps"], omega
44
+ ),
45
+ ),
46
+ }
47
+
48
+ TWO_BUILDERS = {
49
+ "RXX": (
50
+ ("theta", "j1", "k1", "j2", "k2"),
51
+ lambda d, omega, **kw: rxx_matrix_func(
52
+ d, kw["theta"], kw["j1"], kw["k1"], kw["j2"], kw["k2"]
53
+ ),
54
+ ),
55
+ "RZZ": (("theta",), lambda d, omega, **kw: rzz_matrix_func(d, kw["theta"])),
56
+ "CPHASE": (("cv",), lambda d, omega, **kw: cphase_matrix_func(d, kw["cv"], omega)),
57
+ "CSUM": (("cv",), lambda d, omega, **kw: csum_matrix_func(d, kw["cv"])),
58
+ }
59
+
60
+
61
+ def _is_prime(n: int) -> bool:
62
+ """
63
+ Check whether a number is prime.
64
+
65
+ :param n: Integer to test.
66
+ :type n: int
67
+ :return: ``True`` if ``n`` is prime, else ``False``.
68
+ :rtype: bool
69
+ """
70
+ if n < 2:
71
+ return False
72
+ if n in (2, 3, 5, 7):
73
+ return True
74
+ if n % 2 == 0 or n % 3 == 0:
75
+ return False
76
+
77
+ r = int(n**0.5) + 1
78
+ for i in range(5, r, 6):
79
+ if n % i == 0 or n % (i + 2) == 0:
80
+ return False
81
+ return True
82
+
83
+
84
+ def i_matrix_func(d: int) -> Tensor:
85
+ """
86
+ Identity matrix of size ``d``.
87
+
88
+ :param d: Qudit dimension.
89
+ :type d: int
90
+ :return: ``(d, d)`` identity matrix.
91
+ :rtype: Tensor
92
+ """
93
+ return backend.eye(d, dtype=dtypestr)
94
+
95
+
96
+ def x_matrix_func(d: int) -> Tensor:
97
+ r"""
98
+ Generalized Pauli-X on a ``d``-level system.
99
+
100
+ .. math:: X_d\lvert j \rangle = \lvert (j+1) \bmod d \rangle
101
+
102
+ :param d: Qudit dimension.
103
+ :type d: int
104
+ :return: ``(d, d)`` matrix for :math:`X_d`.
105
+ :rtype: Tensor
106
+ """
107
+ m = np.roll(np.eye(d), shift=1, axis=0)
108
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
109
+
110
+
111
+ def z_matrix_func(d: int, omega: Optional[complex] = None) -> Tensor:
112
+ r"""
113
+ Generalized Pauli-Z on a ``d``-level system.
114
+
115
+ .. math:: Z_d\lvert j \rangle = \omega^{j}\lvert j \rangle,\quad \omega=e^{2\pi i/d}
116
+
117
+ :param d: Qudit dimension.
118
+ :type d: int
119
+ :param omega: Optional primitive ``d``-th root of unity. Defaults to :math:`e^{2\pi i/d}`.
120
+ :type omega: Optional[complex]
121
+ :return: ``(d, d)`` matrix for :math:`Z_d`.
122
+ :rtype: Tensor
123
+ """
124
+ omega = np.exp(2j * np.pi / d) if omega is None else omega
125
+ m = np.diag(omega ** np.arange(d))
126
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
127
+
128
+
129
+ def h_matrix_func(d: int, omega: Optional[complex] = None) -> Tensor:
130
+ r"""
131
+ Discrete Fourier transform (Hadamard-like) on ``d`` levels.
132
+
133
+ .. math:: H_d\lvert j \rangle = \frac{1}{\sqrt{d}} \sum_{k=0}^{d-1} \omega^{jk}\lvert k \rangle
134
+
135
+ :param d: Qudit dimension.
136
+ :type d: int
137
+ :param omega: Optional primitive ``d``-th root of unity. Defaults to :math:`e^{2\pi i/d}`.
138
+ :type omega: Optional[complex]
139
+ :return: ``(d, d)`` matrix for :math:`H_d`.
140
+ :rtype: Tensor
141
+ """
142
+ omega = np.exp(2j * np.pi / d) if omega is None else omega
143
+ j, k = np.arange(d), np.arange(d)
144
+ m = omega ** np.outer(j, k) / np.sqrt(d)
145
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
146
+
147
+
148
+ def s_matrix_func(d: int, omega: Optional[complex] = None) -> Tensor:
149
+ r"""
150
+ Diagonal phase gate ``S_d`` on ``d`` levels.
151
+
152
+ .. math:: S_d\lvert j \rangle = \omega^{j(j+p_d)/2}\lvert j \rangle,\quad p_d = (d \bmod 2)
153
+
154
+ :param d: Qudit dimension.
155
+ :type d: int
156
+ :param omega: Optional primitive ``d``-th root of unity. Defaults to :math:`e^{2\pi i/d}`.
157
+ :type omega: Optional[complex]
158
+ :return: ``(d, d)`` diagonal matrix for :math:`S_d`.
159
+ :rtype: Tensor
160
+ """
161
+ omega = np.exp(2j * np.pi / d) if omega is None else omega
162
+ pd = 0 if d % 2 == 0 else 1
163
+
164
+ j = np.arange(d)
165
+ m = np.diag(omega ** ((j * (j + pd)) / 2))
166
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
167
+
168
+
169
+ def _check_rotation_indices(
170
+ d: int, *indices: int, distinct_pairs: bool = False
171
+ ) -> None:
172
+ """
173
+ Validate subspace indices for rotations and interactions.
174
+
175
+ Ensures every index lies in ``[0, d-1]`` and (optionally) that two selected
176
+ basis pairs are distinct.
177
+
178
+ :param d: Qudit dimension.
179
+ :type d: int
180
+ :param indices: Indices to validate (e.g., ``j, k`` or ``j1, k1, j2, k2``).
181
+ :type indices: int
182
+ :param distinct_pairs: If ``True`` and four indices are provided, enforce that
183
+ ``(j1, k1)`` and ``(j2, k2)`` do not denote the same basis state.
184
+ :type distinct_pairs: bool
185
+ :raises ValueError: If any index is out of range or if the distinctness constraint fails.
186
+ """
187
+ for idx in indices:
188
+ if not (0 <= idx < d):
189
+ raise ValueError(f"Index {idx} must satisfy 0 <= index < d (d={d}).")
190
+
191
+ if len(indices) == 2 and indices[0] == indices[1]:
192
+ raise ValueError("Rotation requires two distinct levels: j != k.")
193
+
194
+ if distinct_pairs and len(indices) == 4:
195
+ j1, k1, j2, k2 = indices
196
+ if j1 == k1 and j2 == k2:
197
+ raise ValueError(
198
+ "Selected basis states must be different: (j1, j2) != (k1, k2)."
199
+ )
200
+
201
+
202
+ def _two_level_projectors(
203
+ d: int, j: int, k: Optional[int] = None
204
+ ) -> Tuple[Tensor, ...]:
205
+ r"""
206
+ Construct projectors for single- or two-level subspaces in a ``d``-level qudit.
207
+
208
+ :param d: Qudit dimension.
209
+ :type d: int
210
+ :param j: First level index.
211
+ :type j: int
212
+ :param k: Optional second level index. If None, only projectors for ``j`` are returned.
213
+ :type k: Optional[int]
214
+ :return:
215
+ - If ``k is None``: ``(I, Pjj)``
216
+ - Else: ``(I, Pjj, Pkk, Pjk, Pkj)``
217
+ :rtype: Tuple[Tensor, ...]
218
+ """
219
+ I = backend.eye(d, dtype=dtypestr)
220
+ ej = I[:, j]
221
+ Pjj = backend.outer_product(ej, ej)
222
+
223
+ if k is None:
224
+ return I, Pjj
225
+
226
+ ek = I[:, k]
227
+ Pkk = backend.outer_product(ek, ek)
228
+ Pjk = backend.outer_product(ej, ek)
229
+ Pkj = backend.outer_product(ek, ej)
230
+ return I, Pjj, Pkk, Pjk, Pkj
231
+
232
+
233
+ def rx_matrix_func(d: int, theta: float, j: int = 0, k: int = 1) -> Tensor:
234
+ r"""
235
+ Rotation-X (``RX``) on a selected two-level subspace of a qudit.
236
+
237
+ Acts like the qubit :math:`RX(\theta)` on levels :math:`j` and :math:`k`, identity elsewhere. On the
238
+ :math:`\{\lvert j\rangle,\lvert k\rangle\}` subspace the matrix equals
239
+
240
+ .. math::
241
+ RX(\theta) = \cos\tfrac{\theta}{2}\,(\lvert j\rangle\!\langle j\rvert+\lvert k\rangle\!\langle k\rvert)
242
+ - i\,\sin\tfrac{\theta}{2}\,(\lvert j\rangle\!\langle k\rvert + \lvert k\rangle\!\langle j\rvert)
243
+ + I.
244
+
245
+ :param d: Qudit dimension.
246
+ :type d: int
247
+ :param theta: Rotation angle :math:`\theta`.
248
+ :type theta: float
249
+ :param j: First level index.
250
+ :type j: int
251
+ :param k: Second level index.
252
+ :type k: int
253
+ :return: ``(d, d)`` matrix for :math:`RX(\theta)` on the :math:`j,k` subspace.
254
+ :rtype: Tensor
255
+ """
256
+ _check_rotation_indices(d, j, k)
257
+ I, Pjj, Pkk, Pjk, Pkj = _two_level_projectors(d, j, k)
258
+ theta = num_to_tensor(theta)
259
+ c = backend.cos(theta / 2.0)
260
+ s = backend.sin(theta / 2.0)
261
+ return I + (c - 1.0) * (Pjj + Pkk) + (-1j * s) * (Pjk + Pkj)
262
+
263
+
264
+ def ry_matrix_func(d: int, theta: float, j: int = 0, k: int = 1) -> Tensor:
265
+ r"""
266
+ Rotation-Y (``RY``) on a selected two-level subspace of a qudit.
267
+
268
+ Acts like the qubit :math:`RY(\theta)` on levels :math:`j` and :math:`k`, identity elsewhere. On the
269
+ :math:`\{\lvert j\rangle,\lvert k\rangle\}` subspace the matrix equals
270
+
271
+ .. math::
272
+ RY(\theta) = \cos\tfrac{\theta}{2}\,(\lvert j\rangle\!\langle j\rvert+\lvert k\rangle\!\langle k\rvert)
273
+ + \sin\tfrac{\theta}{2}\,(\lvert k\rangle\!\langle j\rvert - \lvert j\rangle\!\langle k\rvert)
274
+ + I.
275
+
276
+ :param d: Qudit dimension.
277
+ :type d: int
278
+ :param theta: Rotation angle :math:`\theta`.
279
+ :type theta: float
280
+ :param j: First level index.
281
+ :type j: int
282
+ :param k: Second level index.
283
+ :type k: int
284
+ :return: ``(d, d)`` matrix for :math:`RY(\theta)` on the :math:`j,k` subspace.
285
+ :rtype: Tensor
286
+ """
287
+ _check_rotation_indices(d, j, k)
288
+ I, Pjj, Pkk, Pjk, Pkj = _two_level_projectors(d, j, k)
289
+ theta = num_to_tensor(theta)
290
+ c = backend.cos(theta / 2.0)
291
+ s = backend.sin(theta / 2.0)
292
+ return I + (c - 1.0) * (Pjj + Pkk) - s * Pjk + s * Pkj
293
+
294
+
295
+ def rz_matrix_func(d: int, theta: float, j: int = 0) -> Tensor:
296
+ r"""
297
+ Rotation-Z (``RZ``) on a selected level of a qudit.
298
+
299
+ Acts like the qubit :math:`RZ(\theta)` but applies a phase only to level :math:`j`. On the computational
300
+ basis it equals
301
+
302
+ .. math::
303
+ RZ(\theta) = I + (e^{i\theta}-1)\,\lvert j\rangle\!\langle j\rvert,
304
+
305
+ i.e. :math:`\lvert j\rangle \mapsto e^{i\theta}\,\lvert j\rangle` and all other levels unchanged.
306
+
307
+ :param d: Qudit dimension.
308
+ :type d: int
309
+ :param theta: Rotation angle :math:`\theta`.
310
+ :type theta: float
311
+ :param j: Level index receiving the phase.
312
+ :type j: int
313
+ :return: ``(d, d)`` diagonal matrix implementing :math:`RZ(\theta)` on level :math:`j`.
314
+ :rtype: Tensor
315
+ """
316
+ I, Pjj = _two_level_projectors(d, j, k=None)
317
+ theta = num_to_tensor(theta)
318
+ phase = backend.exp(1j * theta)
319
+ return I + (phase - 1.0) * Pjj
320
+
321
+
322
+ def swap_matrix_func(d: int) -> Tensor:
323
+ r"""
324
+ SWAP gate for two qudits of equal dimension :math:`d`.
325
+
326
+ Exchanges basis states :math:`\lvert i\rangle\lvert j\rangle \to \lvert j\rangle\lvert i\rangle`.
327
+
328
+ :param d: Qudit dimension (for each register).
329
+ :type d: int
330
+ :return: ``(d^2, d^2)`` permutation matrix implementing SWAP.
331
+ :rtype: Tensor
332
+ """
333
+ D = d * d
334
+ I = np.eye(D, dtype=dtypestr)
335
+ m = I.reshape(d, d, d, d).transpose(1, 0, 2, 3).reshape(D, D)
336
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
337
+
338
+
339
+ def rzz_matrix_func(
340
+ d: int, theta: float, j1: int = 0, k1: int = 1, j2: int = 0, k2: int = 1
341
+ ) -> Tensor:
342
+ r"""
343
+ Two-qudit ``RZZ(\theta)`` on a selected two-state subspace.
344
+
345
+ Acts like a qubit :math:`RZZ(\theta)=\exp(-i\,\tfrac{\theta}{2}\,\sigma_z)` on the
346
+ two-dimensional subspace spanned by :math:`\lvert j1, j2\rangle` and :math:`\lvert k1, k2\rangle`,
347
+ and as identity elsewhere. The resulting block is diagonal with phases
348
+ :math:`\mathrm{diag}(e^{-i\theta/2},\, e^{+i\theta/2})`.
349
+
350
+ :param d: Dimension of each qudit (assumed equal).
351
+ :type d: int
352
+ :param theta: Rotation angle.
353
+ :type theta: float
354
+ :param j1: Level on qudit-1 for the first basis state.
355
+ :type j1: int
356
+ :param k1: Level on qudit-1 for the second basis state.
357
+ :type k1: int
358
+ :param j2: Level on qudit-2 for the first basis state.
359
+ :type j2: int
360
+ :param k2: Level on qudit-2 for the second basis state.
361
+ :type k2: int
362
+ :return: ``(d*d, d*d)`` matrix representing subspace :math:`RZZ(\theta)`.
363
+ :rtype: Tensor
364
+ :raises ValueError: If indices are out of range or select the same basis state.
365
+ """
366
+ _check_rotation_indices(d, j1, k1, j2, k2, distinct_pairs=True)
367
+ idx_a = j1 * d + j2
368
+ idx_b = k1 * d + k2
369
+ theta = num_to_tensor(theta)
370
+ phase_minus = backend.exp(-1j * theta / 2.0)
371
+ phase_plus = backend.exp(+1j * theta / 2.0)
372
+
373
+ I = backend.eye(d * d, dtype=dtypestr)
374
+ ea = I[:, idx_a]
375
+ eb = I[:, idx_b]
376
+ Paa = backend.outer_product(ea, ea)
377
+ Pbb = backend.outer_product(eb, eb)
378
+ return I + (phase_minus - 1.0) * Paa + (phase_plus - 1.0) * Pbb
379
+
380
+
381
+ def rxx_matrix_func(
382
+ d: int, theta: float, j1: int = 0, k1: int = 1, j2: int = 0, k2: int = 1
383
+ ) -> Tensor:
384
+ r"""
385
+ Two-qudit ``RXX(\theta)`` on a selected two-state subspace.
386
+
387
+ Acts like a qubit :math:`RXX` on the subspace spanned by
388
+ :math:`\lvert j1, j2\rangle` and :math:`\lvert k1, k2\rangle`.
389
+
390
+ :param d: Dimension of each qudit (assumed equal).
391
+ :type d: int
392
+ :param theta: Rotation angle.
393
+ :type theta: float
394
+ :param j1: Level on qudit-1.
395
+ :type j1: int
396
+ :param k1: Level on qudit-1.
397
+ :type k1: int
398
+ :param j2: Level on qudit-2.
399
+ :type j2: int
400
+ :param k2: Level on qudit-2.
401
+ :type k2: int
402
+ :return: ``(d*d, d*d)`` matrix representing :math:`RXX(\theta)` on the selected subspace.
403
+ :rtype: Tensor
404
+ """
405
+ _check_rotation_indices(d, j1, k1, j2, k2, distinct_pairs=True)
406
+ idx_a = j1 * d + j2
407
+ idx_b = k1 * d + k2
408
+ theta = num_to_tensor(theta)
409
+ c = backend.cos(theta / 2.0)
410
+ s = backend.sin(theta / 2.0)
411
+
412
+ I = backend.eye(d * d, dtype=dtypestr)
413
+ ea = I[:, idx_a]
414
+ eb = I[:, idx_b]
415
+ Paa = backend.outer_product(ea, ea)
416
+ Pbb = backend.outer_product(eb, eb)
417
+ Pab = backend.outer_product(ea, eb)
418
+ Pba = backend.outer_product(eb, ea)
419
+ return I + (c - 1.0) * (Paa + Pbb) + (-1j * s) * (Pab + Pba)
420
+
421
+
422
+ def u8_matrix_func(
423
+ d: int,
424
+ gamma: int = 2,
425
+ z: int = 1,
426
+ eps: int = 0,
427
+ omega: Optional[complex] = None,
428
+ ) -> Tensor:
429
+ r"""
430
+ ``U8`` diagonal single-qudit gate for prime dimensions.
431
+
432
+ See ref: Howard, Mark, and Jiri Vala.
433
+ "Qudit versions of the qubit \pi/8 gate." Physical Review A 86, no. 2 (2012): 022316.
434
+ https://doi.org/10.1103/PhysRevA.86.022316
435
+
436
+ This gate is the qudit analogue of the qubit :math:`\pi/8` gate, defined in
437
+ *Howard & Campbell, Phys. Rev. A 86, 022316 (2012)*. It is diagonal in the
438
+ computational basis with exponents determined by modular polynomials in the
439
+ parameters :math:`\gamma, z, \epsilon`. These gates, together with Pauli and
440
+ Clifford operations, generate the full single-qudit Clifford hierarchy.
441
+
442
+ - For :math:`d=2`, this reduces (up to global phase) to the standard qubit
443
+ :math:`\pi/8` gate.
444
+ - For :math:`d=3`, the exponents live in :math:`\mathbb{Z}_9` and the
445
+ primitive ninth root :math:`\zeta = e^{2\pi i/9}` is used.
446
+ - For prime :math:`d>3`, the construction uses the modular inverse of 12 in
447
+ :math:`\mathbb{Z}_d`.
448
+
449
+ :param d: Qudit dimension (must be prime).
450
+ :type d: int
451
+ :param gamma: Shear parameter :math:`\gamma' \in \mathbb{Z}_d`.
452
+ If :math:`gamma = 0`, the gate is a diagonal Clifford.
453
+ If :math:`gamma \neq 0`, the gate is a genuine non-Clifford (analogue of :math:`\pi/8`).
454
+ :type gamma: int
455
+ :param z: Displacement parameter :math:`z' \in \mathbb{Z}_d`,
456
+ which sets the symplectic part of the associated Clifford.
457
+ :type z: int
458
+ :param eps: Phase offset parameter :math:`\epsilon' \in \mathbb{Z}_d`.
459
+ It only contributes a global phase factor :math:`\omega^{\epsilon'}`.
460
+ :type eps: int
461
+ :param omega: Optional primitive :math:`d`-th root of unity (complex).
462
+ Defaults to :math:`e^{2\pi i/d}` for d>3, and :math:`e^{2\pi i/9}` for d=3.
463
+ :type omega: Optional[complex]
464
+ :return: ``(d, d)`` diagonal matrix of dtype ``npdtype``.
465
+ :rtype: Tensor
466
+ :raises ValueError: If ``d`` is not prime; if 12 has no modular inverse
467
+ mod ``d`` (for ``d>3``); or if the computed exponents do not sum to
468
+ 0 mod ``d`` (or 0 mod 3 for ``d=3``).
469
+ """
470
+ if not _is_prime(d):
471
+ raise ValueError(
472
+ f"Dimension d={d} is not prime, U8 gate requires a prime dimension."
473
+ )
474
+
475
+ omega = np.exp(2j * np.pi / d) if omega is None else omega
476
+
477
+ gamma = int(gamma) % d
478
+ z = int(z) % d
479
+ eps = int(eps) % d
480
+
481
+ if d == 3:
482
+ vks = [0, (6 * z + 2 * gamma + 3 * eps) % 9, (6 * z + 1 * gamma + 6 * eps) % 9]
483
+ if sum(vks) % 3 != 0:
484
+ raise ValueError(
485
+ f"Sum of v_k's is not 0 mod 3. Got {sum(vks) % 3}. Check parameters."
486
+ )
487
+
488
+ zeta = np.exp(2j * np.pi / 9)
489
+ m = np.diag([zeta**v for v in vks])
490
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
491
+
492
+ try:
493
+ inv_12 = pow(12, -1, d)
494
+ except ValueError:
495
+ raise ValueError(
496
+ f"Inverse of 12 mod {d} does not exist. Choose a prime d that does not divide 12."
497
+ )
498
+
499
+ vks = [0] * d
500
+ for k in range(1, d):
501
+ term_inner = ((6 * z) % d + ((2 * k - 3) % d) * gamma) % d
502
+ term = (gamma + (k * term_inner) % d) % d
503
+ vk = ((inv_12 * (k % d)) % d) * term % d
504
+ vk = (vk + (eps * (k % d)) % d) % d
505
+ vks[k] = vk
506
+
507
+ if sum(vks) % d != 0:
508
+ raise ValueError(
509
+ f"Sum of v_k's is not 0 mod {d}. Got {sum(vks) % d}. Check parameters."
510
+ )
511
+
512
+ omega = np.exp(2j * np.pi / d) if omega is None else omega
513
+ m = np.diag([omega**v for v in vks])
514
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
515
+
516
+
517
+ def cphase_matrix_func(
518
+ d: int, cv: Optional[int] = None, omega: Optional[complex] = None
519
+ ) -> Tensor:
520
+ r"""
521
+ Qudit controlled-phase (``CPHASE``) gate.
522
+
523
+ Logical definition:
524
+
525
+ .. math::
526
+
527
+ \lvert r \rangle \lvert s \rangle \;\longmapsto\;
528
+ \omega^{rs} \lvert r \rangle \lvert s \rangle
529
+
530
+ Matrix form:
531
+
532
+ .. math::
533
+
534
+ \mathrm{SUMZ}_d =
535
+ \begin{bmatrix}
536
+ I_d & 0 & 0 & \cdots & 0 \\
537
+ 0 & Z_d & 0 & \cdots & 0 \\
538
+ 0 & 0 & Z_d^2 & \cdots & 0 \\
539
+ \vdots & \vdots & \vdots & \ddots & \vdots \\
540
+ 0 & 0 & 0 & \cdots & Z_d^{d-1}
541
+ \end{bmatrix}
542
+
543
+ :param d: Qudit dimension (for each register).
544
+ :type d: int
545
+ :param cv: Optional control value in ``[0, d-1]``. If ``None``, builds the full SUMZ block-diagonal.
546
+ :type cv: Optional[int]
547
+ :param omega: Optional primitive ``d``-th root of unity for ``Z_d``.
548
+ :type omega: Optional[complex]
549
+ :return: ``(d*d, d*d)`` matrix representing the controlled-phase.
550
+ :rtype: Tensor
551
+ :raises ValueError: If ``cv`` is provided and is outside ``[0, d-1]``.
552
+ """
553
+ omega = np.exp(2j * np.pi / d) if omega is None else omega
554
+ r = np.arange(d).reshape(-1, 1)
555
+ s = np.arange(d).reshape(1, -1)
556
+
557
+ if cv is None:
558
+ phase = omega ** (r * s)
559
+ else:
560
+ if not (0 <= cv < d):
561
+ raise ValueError(f"cv must be in [0, {d - 1}], got {cv}")
562
+ phase = 1 + (r == cv) * (omega**s - 1)
563
+
564
+ diag = np.ravel(phase)
565
+ m = np.diag(diag)
566
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
567
+
568
+
569
+ def csum_matrix_func(d: int, cv: Optional[int] = None) -> Tensor:
570
+ r"""
571
+ Qudit controlled-sum (``CSUM`` / ``SUMX``) gate.
572
+
573
+ Logical definition:
574
+
575
+ .. math::
576
+
577
+ \lvert r \rangle \lvert s \rangle \;\longmapsto\;
578
+ \lvert r \rangle \lvert r+s \pmod d \rangle
579
+
580
+ Matrix form:
581
+
582
+ .. math::
583
+
584
+ \mathrm{SUMX}_d =
585
+ \begin{bmatrix}
586
+ I_d & 0 & 0 & \cdots & 0 \\
587
+ 0 & X_d & 0 & \cdots & 0 \\
588
+ 0 & 0 & X_d^2 & \cdots & 0 \\
589
+ \vdots & \vdots & \vdots & \ddots & \vdots \\
590
+ 0 & 0 & 0 & \cdots & X_d^{d-1}
591
+ \end{bmatrix}
592
+
593
+ :param d: Qudit dimension (for each register).
594
+ :type d: int
595
+ :param cv: Optional control value in ``[0, d-1]``. If ``None``, builds the full SUMX block-diagonal.
596
+ :type cv: Optional[int]
597
+ :return: ``(d*d, d*d)`` matrix representing the controlled-sum.
598
+ :rtype: Tensor
599
+ :raises ValueError: If ``cv`` is provided and is outside ``[0, d-1]``.
600
+ """
601
+ I = np.eye(d)
602
+
603
+ if cv is None:
604
+ blocks = [np.roll(I, shift=r, axis=0) for r in range(d)]
605
+ m = np.block(
606
+ [
607
+ [blocks[r] if r == c else np.zeros((d, d)) for c in range(d)]
608
+ for r in range(d)
609
+ ]
610
+ )
611
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)
612
+
613
+ if not (0 <= cv < d):
614
+ raise ValueError(f"cv must be in [0, {d - 1}], got {cv}")
615
+
616
+ X = np.roll(I, shift=1, axis=0)
617
+ m = np.kron(I, I) + np.kron(np.outer(I[:, cv], I[:, cv]), (X - I))
618
+ return backend.cast(backend.convert_to_tensor(m), dtype=dtypestr)