tensorcircuit-nightly 1.2.0.dev20250326__py3-none-any.whl → 1.4.0.dev20251128__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.
- tensorcircuit/__init__.py +5 -1
- tensorcircuit/abstractcircuit.py +4 -0
- tensorcircuit/analogcircuit.py +413 -0
- tensorcircuit/applications/layers.py +1 -1
- tensorcircuit/applications/van.py +1 -1
- tensorcircuit/backends/abstract_backend.py +312 -5
- tensorcircuit/backends/cupy_backend.py +3 -1
- tensorcircuit/backends/jax_backend.py +100 -4
- tensorcircuit/backends/jax_ops.py +108 -0
- tensorcircuit/backends/numpy_backend.py +49 -3
- tensorcircuit/backends/pytorch_backend.py +92 -3
- tensorcircuit/backends/tensorflow_backend.py +102 -3
- tensorcircuit/basecircuit.py +157 -98
- tensorcircuit/circuit.py +115 -57
- tensorcircuit/cloud/local.py +1 -1
- tensorcircuit/cloud/quafu_provider.py +1 -1
- tensorcircuit/cloud/tencent.py +1 -1
- tensorcircuit/compiler/simple_compiler.py +2 -2
- tensorcircuit/cons.py +105 -23
- tensorcircuit/densitymatrix.py +16 -11
- tensorcircuit/experimental.py +733 -153
- tensorcircuit/fgs.py +254 -73
- tensorcircuit/gates.py +66 -22
- tensorcircuit/interfaces/jax.py +5 -3
- tensorcircuit/interfaces/tensortrans.py +6 -2
- tensorcircuit/interfaces/torch.py +14 -4
- tensorcircuit/keras.py +3 -3
- tensorcircuit/mpscircuit.py +154 -65
- tensorcircuit/quantum.py +698 -134
- tensorcircuit/quditcircuit.py +733 -0
- tensorcircuit/quditgates.py +618 -0
- tensorcircuit/results/counts.py +131 -18
- tensorcircuit/results/readout_mitigation.py +4 -1
- tensorcircuit/shadows.py +1 -1
- tensorcircuit/simplify.py +3 -1
- tensorcircuit/stabilizercircuit.py +29 -17
- tensorcircuit/templates/__init__.py +2 -0
- tensorcircuit/templates/blocks.py +2 -2
- tensorcircuit/templates/hamiltonians.py +174 -0
- tensorcircuit/templates/lattice.py +1789 -0
- tensorcircuit/timeevol.py +896 -0
- tensorcircuit/translation.py +10 -3
- tensorcircuit/utils.py +7 -0
- {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.dist-info}/METADATA +66 -29
- tensorcircuit_nightly-1.4.0.dev20251128.dist-info/RECORD +96 -0
- {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.dist-info}/WHEEL +1 -1
- {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.dist-info}/top_level.txt +0 -1
- tensorcircuit_nightly-1.2.0.dev20250326.dist-info/RECORD +0 -118
- tests/__init__.py +0 -0
- tests/conftest.py +0 -67
- tests/test_backends.py +0 -1035
- tests/test_calibrating.py +0 -149
- tests/test_channels.py +0 -409
- tests/test_circuit.py +0 -1699
- tests/test_cloud.py +0 -219
- tests/test_compiler.py +0 -147
- tests/test_dmcircuit.py +0 -555
- tests/test_ensemble.py +0 -72
- tests/test_fgs.py +0 -310
- tests/test_gates.py +0 -156
- tests/test_interfaces.py +0 -562
- tests/test_keras.py +0 -160
- tests/test_miscs.py +0 -282
- tests/test_mpscircuit.py +0 -341
- tests/test_noisemodel.py +0 -156
- tests/test_qaoa.py +0 -86
- tests/test_qem.py +0 -152
- tests/test_quantum.py +0 -549
- tests/test_quantum_attr.py +0 -42
- tests/test_results.py +0 -380
- tests/test_shadows.py +0 -160
- tests/test_simplify.py +0 -46
- tests/test_stabilizer.py +0 -217
- tests/test_templates.py +0 -218
- tests/test_torchnn.py +0 -99
- tests/test_van.py +0 -102
- {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.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
|
+
)
|