tensorcircuit-nightly 1.0.2.dev20250108__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 (76) hide show
  1. tensorcircuit/__init__.py +18 -2
  2. tensorcircuit/about.py +46 -0
  3. tensorcircuit/abstractcircuit.py +4 -0
  4. tensorcircuit/analogcircuit.py +413 -0
  5. tensorcircuit/applications/layers.py +1 -1
  6. tensorcircuit/applications/van.py +1 -1
  7. tensorcircuit/backends/abstract_backend.py +320 -7
  8. tensorcircuit/backends/cupy_backend.py +3 -1
  9. tensorcircuit/backends/jax_backend.py +102 -4
  10. tensorcircuit/backends/jax_ops.py +110 -1
  11. tensorcircuit/backends/numpy_backend.py +49 -3
  12. tensorcircuit/backends/pytorch_backend.py +92 -3
  13. tensorcircuit/backends/tensorflow_backend.py +102 -3
  14. tensorcircuit/basecircuit.py +157 -98
  15. tensorcircuit/circuit.py +115 -57
  16. tensorcircuit/cloud/local.py +1 -1
  17. tensorcircuit/cloud/quafu_provider.py +1 -1
  18. tensorcircuit/cloud/tencent.py +1 -1
  19. tensorcircuit/compiler/simple_compiler.py +2 -2
  20. tensorcircuit/cons.py +142 -21
  21. tensorcircuit/densitymatrix.py +43 -14
  22. tensorcircuit/experimental.py +387 -129
  23. tensorcircuit/fgs.py +282 -81
  24. tensorcircuit/gates.py +66 -22
  25. tensorcircuit/interfaces/__init__.py +1 -3
  26. tensorcircuit/interfaces/jax.py +189 -0
  27. tensorcircuit/keras.py +3 -3
  28. tensorcircuit/mpscircuit.py +154 -65
  29. tensorcircuit/quantum.py +868 -152
  30. tensorcircuit/quditcircuit.py +733 -0
  31. tensorcircuit/quditgates.py +618 -0
  32. tensorcircuit/results/counts.py +147 -20
  33. tensorcircuit/results/readout_mitigation.py +4 -1
  34. tensorcircuit/shadows.py +1 -1
  35. tensorcircuit/simplify.py +3 -1
  36. tensorcircuit/stabilizercircuit.py +479 -0
  37. tensorcircuit/templates/__init__.py +2 -0
  38. tensorcircuit/templates/blocks.py +2 -2
  39. tensorcircuit/templates/hamiltonians.py +174 -0
  40. tensorcircuit/templates/lattice.py +1789 -0
  41. tensorcircuit/timeevol.py +896 -0
  42. tensorcircuit/translation.py +10 -3
  43. tensorcircuit/utils.py +7 -0
  44. {tensorcircuit_nightly-1.0.2.dev20250108.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/METADATA +73 -23
  45. tensorcircuit_nightly-1.4.0.dev20251103.dist-info/RECORD +96 -0
  46. {tensorcircuit_nightly-1.0.2.dev20250108.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/WHEEL +1 -1
  47. {tensorcircuit_nightly-1.0.2.dev20250108.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/top_level.txt +0 -1
  48. tensorcircuit_nightly-1.0.2.dev20250108.dist-info/RECORD +0 -115
  49. tests/__init__.py +0 -0
  50. tests/conftest.py +0 -67
  51. tests/test_backends.py +0 -1031
  52. tests/test_calibrating.py +0 -149
  53. tests/test_channels.py +0 -365
  54. tests/test_circuit.py +0 -1699
  55. tests/test_cloud.py +0 -219
  56. tests/test_compiler.py +0 -147
  57. tests/test_dmcircuit.py +0 -555
  58. tests/test_ensemble.py +0 -72
  59. tests/test_fgs.py +0 -310
  60. tests/test_gates.py +0 -156
  61. tests/test_interfaces.py +0 -429
  62. tests/test_keras.py +0 -160
  63. tests/test_miscs.py +0 -277
  64. tests/test_mpscircuit.py +0 -341
  65. tests/test_noisemodel.py +0 -156
  66. tests/test_qaoa.py +0 -86
  67. tests/test_qem.py +0 -152
  68. tests/test_quantum.py +0 -526
  69. tests/test_quantum_attr.py +0 -42
  70. tests/test_results.py +0 -347
  71. tests/test_shadows.py +0 -160
  72. tests/test_simplify.py +0 -46
  73. tests/test_templates.py +0 -218
  74. tests/test_torchnn.py +0 -99
  75. tests/test_van.py +0 -102
  76. {tensorcircuit_nightly-1.0.2.dev20250108.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,733 @@
1
+ """
2
+ Quantum circuit: state simulator for **qudits** (d-level systems).
3
+
4
+ This module provides a high-level `QuditCircuit` API that mirrors `tensorcircuit.circuit.Circuit`
5
+ but targets qudits with dimension `3 <= d <= 36`.
6
+ For string-encoded samples/counts, digits use `0-9A-Z` where `A=10, ..., Z=35`.
7
+
8
+ .. note::
9
+ For qubits (`d=2`) please use :class:`tensorcircuit.circuit.Circuit`.
10
+
11
+ """
12
+
13
+ from functools import partial
14
+ from typing import Any, Dict, List, Optional, Tuple, Sequence, Union, Literal
15
+
16
+ import numpy as np
17
+ import tensornetwork as tn
18
+
19
+ from .gates import Gate
20
+ from .utils import arg_alias
21
+ from .basecircuit import BaseCircuit
22
+ from .circuit import Circuit
23
+ from .quantum import QuOperator, QuVector
24
+ from .quditgates import SINGLE_BUILDERS, TWO_BUILDERS
25
+
26
+
27
+ Tensor = Any
28
+ SAMPLE_FORMAT = Literal["sample_bin", "count_dict_bin"]
29
+
30
+
31
+ class QuditCircuit:
32
+ r"""
33
+ The `QuditCircuit` class provides a d-dimensional state-vector simulator and a thin wrapper
34
+ around :class:`tensorcircuit.circuit.Circuit`, exposing a qudit-friendly API and docstrings.
35
+
36
+ **Quick example (d=3):**
37
+
38
+ >>> c = tc.QuditCircuit(2, dim=3)
39
+ >>> c.h(0)
40
+ >>> c.x(1)
41
+ >>> c.csum(0, 1)
42
+ >>> c.sample(1024, format="count_dict_bin")
43
+
44
+ .. note::
45
+ For `3 <= d <= 36`, string samples and count keys use base-`d` characters `0-9A-Z`
46
+ (`A=10, ..., Z=35`).
47
+
48
+ :param nqudits: Number of qudits (wires) in the circuit.
49
+ :type nqudits: int
50
+ :param dim: Qudit local dimension `d`. Must satisfy `3 <= d <= 36`.
51
+ :type dim: int
52
+ :param inputs: Optional initial state as a wavefunction.
53
+ :type inputs: Optional[Tensor]
54
+ :param mps_inputs: Optional initial state in MPS/MPO-like form.
55
+ :type mps_inputs: Optional[QuOperator]
56
+ :param split: Internal contraction/splitting configuration passed through to
57
+ :class:`~tensorcircuit.circuit.Circuit`.
58
+ :type split: Optional[Dict[str, Any]]
59
+
60
+ :var is_dm: Whether the simulator is a density-matrix simulator (`False` here).
61
+ :vartype is_dm: bool
62
+ :var dim: Property for the local dimension `d`.
63
+ :vartype dim: int
64
+ :var nqudits: Property for the number of qudits.
65
+ :vartype nqudits: int
66
+ """
67
+
68
+ is_dm = False
69
+
70
+ def __init__(
71
+ self,
72
+ nqudits: int,
73
+ dim: int,
74
+ inputs: Optional[Tensor] = None,
75
+ mps_inputs: Optional[QuOperator] = None,
76
+ split: Optional[Dict[str, Any]] = None,
77
+ ):
78
+ self._set_dim(dim=dim)
79
+ self._nqudits = nqudits
80
+
81
+ self._circ = Circuit(
82
+ nqubits=nqudits,
83
+ dim=dim,
84
+ inputs=inputs,
85
+ mps_inputs=mps_inputs,
86
+ split=split,
87
+ )
88
+ self._omega = np.exp(2j * np.pi / self._d)
89
+ self.circuit_param = self._circ.circuit_param
90
+
91
+ def _set_dim(self, dim: int) -> None:
92
+ if not isinstance(dim, int) or dim <= 2:
93
+ raise ValueError(
94
+ f"QuditCircuit is only for qudits (dim>=3). "
95
+ f"You passed dim={dim}. For qubits, please use `Circuit` instead."
96
+ )
97
+ # Require integer d>=2; current string-encoded IO supports d<=36 (0-9A-Z digits).
98
+ if dim > 36:
99
+ raise NotImplementedError(
100
+ "The Qudit interface is only supported for dimension < 36 now."
101
+ )
102
+ self._d = dim
103
+
104
+ @property
105
+ def dim(self) -> int:
106
+ """dimension of the qudit circuit"""
107
+ return self._d
108
+
109
+ @property
110
+ def nqudits(self) -> int:
111
+ """qudit number of the circuit"""
112
+ return self._nqudits
113
+
114
+ def _apply_gate(self, *indices: int, name: str, **kwargs: Any) -> None:
115
+ """
116
+ Apply a single- or two-qudit unitary by name.
117
+
118
+ The gate matrix is looked up by name in either :data:`SINGLE_BUILDERS` (single-qudit)
119
+ or :data:`TWO_BUILDERS` (two-qudit). The registered builder is called with `(d, omega, **kwargs)`
120
+ to produce the unitary, which is then applied at the given indices.
121
+
122
+ :param indices: Qudit indices the gate acts on. One index -> single-qudit gate; two indices -> two-qudit gate.
123
+ :type indices: int
124
+ :param name: Gate name registered in the corresponding builder set.
125
+ :type name: str
126
+ :param kwargs: Extra parameters forwarded to the builder (matched by keyword).
127
+ :type kwargs: Any
128
+ :raises ValueError: If the name is unknown or the arity does not match the number of indices.
129
+ """
130
+ if len(indices) == 1 and name in SINGLE_BUILDERS:
131
+ sig, builder = SINGLE_BUILDERS[name]
132
+ extras = tuple(kwargs.get(k) for k in sig if k != "none")
133
+ builder_kwargs = {k: v for k, v in zip(sig, extras)}
134
+ mat = builder(self._d, self._omega, **builder_kwargs)
135
+ self._circ.unitary(*indices, unitary=mat, name=name, dim=self._d) # type: ignore
136
+ elif len(indices) == 2 and name in TWO_BUILDERS:
137
+ sig, builder = TWO_BUILDERS[name]
138
+ extras = tuple(kwargs.get(k) for k in sig if k != "none")
139
+ builder_kwargs = {k: v for k, v in zip(sig, extras)}
140
+ mat = builder(self._d, self._omega, **builder_kwargs)
141
+ self._circ.unitary( # type: ignore
142
+ *indices, unitary=mat, name=name, dim=self._d
143
+ )
144
+ else:
145
+ raise ValueError(f"Unsupported gate/arity: {name} on {len(indices)} qudits")
146
+
147
+ def any(self, *indices: int, unitary: Tensor, name: Optional[str] = None) -> None:
148
+ """
149
+ Apply an arbitrary unitary on one or two qudits.
150
+
151
+ :param indices: Target qudit indices.
152
+ :type indices: int
153
+ :param unitary: Unitary matrix acting on the specified qudit(s), with shape `(d, d)` or `(d^2, d^2)`.
154
+ :type unitary: Tensor
155
+ :param name: Optional label stored in the circuit history.
156
+ :type name: str
157
+ """
158
+ name = "any" if name is None else name
159
+ self._circ.unitary(*indices, unitary=unitary, name=name, dim=self._d) # type: ignore
160
+
161
+ unitary = any
162
+
163
+ def i(self, index: int) -> None:
164
+ """
165
+ Apply the generalized identity gate `I` on the given qudit.
166
+
167
+ :param index: Qudit index.
168
+ :type index: int
169
+ """
170
+ self._apply_gate(index, name="I")
171
+
172
+ I = i
173
+
174
+ def x(self, index: int) -> None:
175
+ """
176
+ Apply the generalized shift gate `X` on the given qudit (adds `+1 mod d`).
177
+
178
+ :param index: Qudit index.
179
+ :type index: int
180
+ """
181
+ self._apply_gate(index, name="X")
182
+
183
+ X = x
184
+
185
+ # def y(self, index: int) -> None:
186
+ # """
187
+ # Apply the Y gate on the given qudit index.
188
+ #
189
+ # :param index: Qudit index to apply the gate on.
190
+ # :type index: int
191
+ # """
192
+ # self._apply_gate(index, name="Y")
193
+
194
+ def z(self, index: int) -> None:
195
+ """
196
+ Apply the generalized phase gate `Z` on the given qudit (multiplies by `omega^k`).
197
+
198
+ :param index: Qudit index.
199
+ :type index: int
200
+ """
201
+ self._apply_gate(index, name="Z")
202
+
203
+ Z = z
204
+
205
+ def h(self, index: int) -> None:
206
+ """
207
+ Apply the generalized Hadamard-like gate `H` (DFT on `d` levels) on the given qudit.
208
+
209
+ :param index: Qudit index.
210
+ :type index: int
211
+ """
212
+ self._apply_gate(index, name="H")
213
+
214
+ H = h
215
+
216
+ def u8(
217
+ self, index: int, gamma: float = 2.0, z: float = 1.0, eps: float = 0.0
218
+ ) -> None:
219
+ """
220
+ Apply the parameterized single-qudit gate `U8`.
221
+
222
+ :param index: Qudit index.
223
+ :type index: int
224
+ :param gamma: Gate parameter `gamma`.
225
+ :type gamma: float
226
+ :param z: Gate parameter `z`.
227
+ :type z: float
228
+ :param eps: Gate parameter `eps`.
229
+ :type eps: float
230
+ """
231
+ self._apply_gate(index, name="U8", extra=(gamma, z, eps))
232
+
233
+ U8 = u8
234
+
235
+ def rx(self, index: int, theta: float, j: int = 0, k: int = 1) -> None:
236
+ """
237
+ Single-qudit rotation `RX` on a selected two-level subspace `(j, k)`.
238
+
239
+ :param index: Qudit index.
240
+ :type index: int
241
+ :param theta: Rotation angle.
242
+ :type theta: float
243
+ :param j: Source level of the rotation subspace (0-based).
244
+ :type j: int
245
+ :param k: Target level of the rotation subspace (0-based).
246
+ :type k: int
247
+ :raises ValueError: If `j == k` or indices are outside `[0, d-1]`.
248
+ """
249
+ self._apply_gate(index, name="RX", theta=theta, j=j, k=k)
250
+
251
+ RX = rx
252
+
253
+ def ry(self, index: int, theta: float, j: int = 0, k: int = 1) -> None:
254
+ """
255
+ Single-qudit rotation `RY` on a selected two-level subspace `(j, k)`.
256
+
257
+ :param index: Qudit index.
258
+ :type index: int
259
+ :param theta: Rotation angle.
260
+ :type theta: float
261
+ :param j: Source level of the rotation subspace (0-based).
262
+ :type j: int
263
+ :param k: Target level of the rotation subspace (0-based).
264
+ :type k: int
265
+ :raises ValueError: If `j == k` or indices are outside `[0, d-1]`.
266
+ """
267
+ self._apply_gate(index, name="RY", theta=theta, j=j, k=k)
268
+
269
+ RY = ry
270
+
271
+ def rz(self, index: int, theta: float, j: int = 0) -> None:
272
+ """
273
+ Single-qudit phase rotation `RZ` applied on level `j`.
274
+
275
+ :param index: Qudit index.
276
+ :type index: int
277
+ :param theta: Phase rotation angle around Z.
278
+ :type theta: float
279
+ :param j: Level where the phase is applied (0-based).
280
+ :type j: int
281
+ :raises ValueError: If `j` is outside `[0, d-1]`.
282
+ """
283
+ self._apply_gate(index, name="RZ", theta=theta, j=j)
284
+
285
+ RZ = rz
286
+
287
+ def rxx(
288
+ self,
289
+ *indices: int,
290
+ theta: float,
291
+ j1: int = 0,
292
+ k1: int = 1,
293
+ j2: int = 0,
294
+ k2: int = 1,
295
+ ) -> None:
296
+ """
297
+ Two-qudit interaction `RXX` acting on subspaces `(j1, k1)` and `(j2, k2)`.
298
+
299
+ :param indices: Two qudit indices `(q1, q2)`.
300
+ :type indices: int
301
+ :param theta: Interaction angle.
302
+ :type theta: float
303
+ :param j1: Source level of the first qudit subspace.
304
+ :type j1: int
305
+ :param k1: Target level of the first qudit subspace.
306
+ :type k1: int
307
+ :param j2: Source level of the second qudit subspace.
308
+ :type j2: int
309
+ :param k2: Target level of the second qudit subspace.
310
+ :type k2: int
311
+ :raises ValueError: If levels are invalid or the arity is not two.
312
+ """
313
+ self._apply_gate(*indices, name="RXX", theta=theta, j1=j1, k1=k1, j2=j2, k2=k2)
314
+
315
+ RXX = rxx
316
+
317
+ def rzz(
318
+ self,
319
+ *indices: int,
320
+ theta: float,
321
+ j1: int = 0,
322
+ k1: int = 1,
323
+ j2: int = 0,
324
+ k2: int = 1,
325
+ ) -> None:
326
+ """
327
+ Two-qudit interaction `RZZ` acting on subspaces `(j1, k1)` and `(j2, k2)`.
328
+
329
+ :param indices: Two qudit indices `(q1, q2)`.
330
+ :type indices: int
331
+ :param theta: Interaction angle.
332
+ :type theta: float
333
+ :param j1: Source level of the first qudit subspace.
334
+ :type j1: int
335
+ :param k1: Target level of the first qudit subspace.
336
+ :type k1: int
337
+ :param j2: Source level of the second qudit subspace.
338
+ :type j2: int
339
+ :param k2: Target level of the second qudit subspace.
340
+ :type k2: int
341
+ :raises ValueError: If levels are invalid or the arity is not two.
342
+ """
343
+ self._apply_gate(*indices, name="RZZ", theta=theta, j1=j1, k1=k1, j2=j2, k2=k2)
344
+
345
+ RZZ = rzz
346
+
347
+ def cphase(self, *indices: int, cv: Optional[int] = None) -> None:
348
+ """
349
+ Apply a controlled-phase gate `CPHASE`.
350
+
351
+ :param indices: Two qudit indices `(control, target)`.
352
+ :type indices: int
353
+ :param cv: Optional control value. If `None`, defaults to `1`.
354
+ :type cv: Optional[int]
355
+ :raises ValueError: If arity is not two.
356
+ """
357
+ self._apply_gate(*indices, name="CPHASE", cv=cv)
358
+
359
+ CPHASE = cphase
360
+
361
+ def csum(self, *indices: int, cv: Optional[int] = None) -> None:
362
+ """
363
+ Apply a generalized controlled-sum gate `CSUM` (a.k.a. qudit CNOT).
364
+
365
+ :param indices: Two qudit indices `(control, target)`.
366
+ :type indices: int
367
+ :param cv: Optional control value. If `None`, defaults to `1`.
368
+ :type cv: Optional[int]
369
+ :raises ValueError: If arity is not two.
370
+ """
371
+ self._apply_gate(*indices, name="CSUM", cv=cv)
372
+
373
+ cnot, CSUM, CNOT = csum, csum, csum
374
+
375
+ # Functional
376
+ def wavefunction(self, form: str = "default") -> tn.Node.tensor:
377
+ """
378
+ Compute the output wavefunction from the circuit.
379
+
380
+ :param form: The str indicating the form of the output wavefunction.
381
+ "default": [-1], "ket": [-1, 1], "bra": [1, -1]
382
+ :type form: str, optional
383
+ :return: Tensor with the corresponding shape.
384
+ :rtype: Tensor
385
+ """
386
+ return self._circ.wavefunction(form)
387
+
388
+ state = wavefunction
389
+
390
+ def get_quoperator(self) -> QuOperator:
391
+ """
392
+ Get the ``QuOperator`` MPO like representation of the circuit unitary without contraction.
393
+
394
+ :return: ``QuOperator`` object for the circuit unitary (open indices for the input state)
395
+ :rtype: QuOperator
396
+ """
397
+ return self._circ.quoperator()
398
+
399
+ quoperator = get_quoperator
400
+
401
+ get_circuit_as_quoperator = get_quoperator
402
+ get_state_as_quvector = BaseCircuit.quvector
403
+
404
+ def matrix(self) -> Tensor:
405
+ """
406
+ Get the unitary matrix for the circuit irrespective with the circuit input state.
407
+
408
+ :return: The circuit unitary matrix
409
+ :rtype: Tensor
410
+ """
411
+ return self._circ.matrix()
412
+
413
+ # def measure_reference(
414
+ # self, *index: int, with_prob: bool = False
415
+ # ) -> Tuple[str, float]:
416
+ # return self._circ.measure_reference(*index, with_prob=with_prob)
417
+
418
+ def expectation(
419
+ self,
420
+ *ops: Tuple[tn.Node, List[int]],
421
+ reuse: bool = True,
422
+ enable_lightcone: bool = False,
423
+ nmc: int = 1000,
424
+ **kws: Any,
425
+ ) -> Tensor:
426
+ """
427
+ Compute expectation(s) of local operators.
428
+
429
+ :param ops: Pairs of `(operator_node, [sites])` specifying where each operator acts.
430
+ :type ops: Tuple[tn.Node, List[int]]
431
+ :param reuse: If True, then the wavefunction tensor is cached for further expectation evaluation,
432
+ defaults to be true.
433
+ :type reuse: bool, optional
434
+ :param enable_lightcone: whether enable light cone simplification, defaults to False
435
+ :type enable_lightcone: bool, optional
436
+ :param nmc: repetition time for Monte Carlo sampling for noisfy calculation, defaults to 1000
437
+ :type nmc: int, optional
438
+ :raises ValueError: "Cannot measure two operators in one index"
439
+ :return: Tensor with one element
440
+ :rtype: Tensor
441
+ """
442
+ return self._circ.expectation(
443
+ *ops,
444
+ reuse=reuse,
445
+ enable_lightcone=enable_lightcone,
446
+ noise_conf=None,
447
+ nmc=nmc,
448
+ **kws,
449
+ )
450
+
451
+ def measure_jit(
452
+ self, *index: int, with_prob: bool = False, status: Optional[Tensor] = None
453
+ ) -> Tuple[Tensor, Tensor]:
454
+ """
455
+ Take measurement on the given site indices (computational basis).
456
+ This method is jittable!
457
+
458
+ :param index: Measure on which site (wire) index.
459
+ :type index: int
460
+ :param with_prob: If true, theoretical probability is also returned.
461
+ :type with_prob: bool, optional
462
+ :param status: external randomness, with shape [index], defaults to None
463
+ :type status: Optional[Tensor]
464
+ :return: The sample output and probability (optional) of the quantum line.
465
+ :rtype: Tuple[Tensor, Tensor]
466
+ """
467
+ return self._circ.measure_jit(*index, with_prob=with_prob, status=status)
468
+
469
+ measure = measure_jit
470
+
471
+ def amplitude(self, l: Union[str, Tensor]) -> Tensor:
472
+ r"""
473
+ Return the amplitude for a given base-`d` string `l`.
474
+
475
+ For state simulators, this computes :math:`\langle l \vert \psi \rangle`.
476
+ For density-matrix simulators, it would compute :math:`\operatorname{Tr}(\rho \vert l \rangle \langle l \vert)`
477
+ (note the square in magnitude differs between the two formalisms).
478
+
479
+ **Example**
480
+
481
+ >>> c = tc.QuditCircuit(2, dim=3)
482
+ >>> c.x(0)
483
+ >>> c.x(1)
484
+ >>> c.amplitude("20")
485
+ array(1.+0.j, dtype=complex64)
486
+ >>> c.csum(0, 1, cv=2)
487
+ >>> c.amplitude("21")
488
+ array(1.+0.j, dtype=complex64)
489
+
490
+ :param l: Bitstring in base-`d` using `0-9A-Z`.
491
+ :type l: Union[str, Tensor]
492
+ :return: Complex amplitude.
493
+ :rtype: Tensor
494
+ """
495
+ return self._circ.amplitude(l)
496
+
497
+ def probability(self) -> Tensor:
498
+ """
499
+ Get the length-`d^n` probability vector over the computational basis.
500
+
501
+ :return: Probability vector of shape `[dim^n]`.
502
+ :rtype: Tensor
503
+ """
504
+ return self._circ.probability()
505
+
506
+ @partial(arg_alias, alias_dict={"format": ["format_"]})
507
+ def sample(
508
+ self,
509
+ batch: Optional[int] = None,
510
+ allow_state: bool = False,
511
+ readout_error: Optional[Sequence[Any]] = None,
512
+ format: Optional[SAMPLE_FORMAT] = None,
513
+ random_generator: Optional[Any] = None,
514
+ status: Optional[Tensor] = None,
515
+ jittable: bool = True,
516
+ ) -> Any:
517
+ r"""
518
+ Batched sampling from the circuit or final state.
519
+
520
+ :param batch: Number of samples. If `None`, returns a single draw.
521
+ :type batch: Optional[int]
522
+ :param allow_state: If `True`, sample from the final state (when memory allows). Prefer `True` for speed.
523
+ :type allow_state: bool
524
+ :param readout_error: Optional readout error model.
525
+ :type readout_error: Optional[Sequence[Any]]
526
+ :param format: Output format. See :py:meth:`tensorcircuit.quantum.measurement_results`.
527
+ Supported formats for qudits include:
528
+
529
+ "count_vector": # np.array([2, 0, 0, 0])
530
+
531
+ "count_dict_bin": # {"00": 2, "01": 0, "10": 0, "11": 0}
532
+ for cases :math:`d\in [11, 36]`, use 0-9A-Z digits (e.g., 'A' -> 10, ..., 'Z' -> 35);
533
+
534
+ :type format: Optional[str]
535
+ :param random_generator: random generator, defaults to None
536
+ :type random_generator: Optional[Any], optional
537
+ :param status: external randomness given by tensor uniformly from [0, 1],
538
+ if set, can overwrite random_generator, shape [batch] for `allow_state=True`
539
+ and shape [batch, nqudits] for `allow_state=False` using perfect sampling implementation
540
+ :type status: Optional[Tensor]
541
+ :param jittable: when converting to count, whether keep the full size. if false, may be conflict
542
+ external jit, if true, may fail for large scale system with actual limited count results
543
+ :type jittable: bool, defaults true
544
+ :return: List (if batch) of tuple (binary configuration tensor and corresponding probability)
545
+ if the format is None, and consistent with format when given
546
+ :rtype: Any
547
+ :raises NotImplementedError: For integer-based output formats not suitable for qudits.
548
+ """
549
+ if format in ["sample_int", "count_tuple", "count_dict_int", "count_vector"]:
550
+ raise NotImplementedError(
551
+ "`int` representation is not friendly for d-dimensional systems."
552
+ )
553
+ return self._circ.sample(
554
+ batch=batch,
555
+ allow_state=allow_state,
556
+ readout_error=readout_error,
557
+ format=format,
558
+ random_generator=random_generator,
559
+ status=status,
560
+ jittable=jittable,
561
+ )
562
+
563
+ def projected_subsystem(self, traceout: Tensor, left: Tuple[int, ...]) -> Tensor:
564
+ """
565
+ remaining wavefunction or density matrix on sites in `left`, while fixing the other
566
+ sites to given digits as indicated by `traceout`.
567
+
568
+ :param traceout: A tensor encoding digits (0..d-1) for sites to be fixed; jittable.
569
+ :type traceout: Tensor
570
+ :param left: Tuple of site indices to keep (non-jittable argument).
571
+ :type left: Tuple[int, ...]
572
+ :return: Remaining wavefunction or density matrix on the kept sites.
573
+ :rtype: Tensor
574
+ """
575
+ return self._circ.projected_subsystem(
576
+ traceout=traceout,
577
+ left=left,
578
+ )
579
+
580
+ def replace_inputs(self, inputs: Tensor) -> None:
581
+ """
582
+ Replace the input state with the circuit structure unchanged.
583
+
584
+ :param inputs: Input wavefunction.
585
+ :type inputs: Tensor
586
+ """
587
+ return self._circ.replace_inputs(inputs)
588
+
589
+ def mid_measurement(self, index: int, keep: int = 0) -> Tensor:
590
+ """
591
+ Mid-circuit Z-basis post-selection.
592
+
593
+ The returned state is **not normalized**; normalize manually if needed.
594
+
595
+ :param index: Qudit index where post-selection is applied.
596
+ :type index: int
597
+ :param keep: Post-selected digit in `{0, ..., d-1}`.
598
+ :type keep: int
599
+ :return: Unnormalized post-selected state.
600
+ :rtype: Tensor
601
+ """
602
+ return self._circ.mid_measurement(index, keep=keep)
603
+
604
+ mid_measure = mid_measurement
605
+ post_select = mid_measurement
606
+ post_selection = mid_measurement
607
+
608
+ def get_quvector(self) -> QuVector:
609
+ """
610
+ Get the representation of the output state in the form of ``QuVector``
611
+ while maintaining the circuit uncomputed
612
+
613
+ :return: ``QuVector`` representation of the output state from the circuit
614
+ :rtype: QuVector
615
+ """
616
+ return self._circ.quvector()
617
+
618
+ quvector = get_quvector
619
+
620
+ def replace_mps_inputs(self, mps_inputs: QuOperator) -> None:
621
+ """
622
+ Replace the input state (keeps circuit structure) using an MPS/MPO-like representation.
623
+
624
+ **Example**
625
+
626
+ >>> c = tc.QuditCircuit(2, dim=3)
627
+ >>> c.x(0)
628
+ >>> c2 = tc.QuditCircuit(2, dim=3, mps_inputs=c.quvector())
629
+ >>> c2.x(0); c2.wavefunction()
630
+ array([...], dtype=complex64)
631
+ >>> c3 = tc.QuditCircuit(2, dim=3)
632
+ >>> c3.x(0)
633
+ >>> c3.replace_mps_inputs(c.quvector()); c3.wavefunction()
634
+ array([...], dtype=complex64)
635
+
636
+ :param mps_inputs: (Nodes, dangling Edges) for a MPS like initial wavefunction.
637
+ :type mps_inputs: Tuple[Sequence[Gate], Sequence[Edge]]
638
+ """
639
+ return self._circ.replace_mps_inputs(mps_inputs)
640
+
641
+ def expectation_before(
642
+ self,
643
+ *ops: Tuple[tn.Node, List[int]],
644
+ reuse: bool = True,
645
+ **kws: Any,
646
+ ) -> List[tn.Node]:
647
+ """
648
+ Build (but do not contract) the tensor network for expectation evaluation.
649
+
650
+ :param reuse: _description_, defaults to True
651
+ :type reuse: bool, optional
652
+ :raises ValueError: _description_
653
+ :return: _description_
654
+ :rtype: List[tn.Node]
655
+ """
656
+ return self._circ.expectation_before(*ops, reuse=reuse, **kws)
657
+
658
+ def amplitude_before(self, l: Union[str, Tensor]) -> List[Gate]:
659
+ r"""
660
+ Return the (uncontracted) tensor network nodes for the amplitude of configuration `l`.
661
+
662
+ For state simulators, this corresponds to :math:`\langle l \vert \psi \rangle`.
663
+ For density-matrix simulators,
664
+ it would correspond to :math:`\operatorname{Tr}(\rho \vert l \rangle \langle l \vert)`.
665
+
666
+ :param l: Base-`d` string using `0-9A-Z` or an equivalent tensor index.
667
+ :type l: Union[str, Tensor]
668
+ :return: The tensornetwork nodes for the amplitude of the circuit.
669
+ :rtype: List[Gate]
670
+ """
671
+ return self._circ.amplitude_before(l)
672
+
673
+ def general_kraus(
674
+ self,
675
+ kraus: Sequence[Gate],
676
+ *index: int,
677
+ status: Optional[float] = None,
678
+ with_prob: bool = False,
679
+ name: Optional[str] = None,
680
+ ) -> Tensor:
681
+ """
682
+ Monte Carlo trajectory simulation of general Kraus channel whose Kraus operators cannot be
683
+ amplified to unitary operators. For unitary operators composed Kraus channel, :py:meth:`unitary_kraus`
684
+ is much faster.
685
+
686
+ This function is jittable in theory. But only jax+GPU combination is recommended for jit
687
+ since the graph building time is too long for other backend options; though the running
688
+ time of the function is very fast for every case.
689
+
690
+ :param kraus: A list of ``tn.Node`` for Kraus operators.
691
+ :type kraus: Sequence[Gate]
692
+ :param index: The qubits index that Kraus channel is applied on.
693
+ :type index: int
694
+ :param status: Random tensor uniformly between 0 or 1, defaults to be None,
695
+ when the random number will be generated automatically
696
+ :type status: Optional[float], optional
697
+ """
698
+ return self._circ.general_kraus(
699
+ kraus,
700
+ *index,
701
+ status=status,
702
+ with_prob=with_prob,
703
+ name=name,
704
+ )
705
+
706
+ def unitary_kraus(
707
+ self,
708
+ kraus: Sequence[Gate],
709
+ *index: int,
710
+ prob: Optional[Sequence[float]] = None,
711
+ status: Optional[float] = None,
712
+ name: Optional[str] = None,
713
+ ) -> Tensor:
714
+ """
715
+ Apply unitary gates in ``kraus`` randomly based on corresponding ``prob``.
716
+ If ``prob`` is ``None``, this is reduced to kraus channel language.
717
+
718
+ :param kraus: List of ``tc.gates.Gate`` or just Tensors
719
+ :type kraus: Sequence[Gate]
720
+ :param prob: prob list with the same size as ``kraus``, defaults to None
721
+ :type prob: Optional[Sequence[float]], optional
722
+ :param status: random seed between 0 to 1, defaults to None
723
+ :type status: Optional[float], optional
724
+ :return: shape [] int dtype tensor indicates which kraus gate is actually applied
725
+ :rtype: Tensor
726
+ """
727
+ return self._circ.unitary_kraus(
728
+ kraus,
729
+ *index,
730
+ prob=prob,
731
+ status=status,
732
+ name=name,
733
+ )