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.
- 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 +92 -3
- 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 +123 -82
- tensorcircuit/circuit.py +67 -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 +1 -0
- tensorcircuit/densitymatrix.py +16 -11
- tensorcircuit/experimental.py +7 -152
- tensorcircuit/fgs.py +5 -6
- tensorcircuit/gates.py +66 -22
- tensorcircuit/keras.py +3 -3
- tensorcircuit/mpscircuit.py +109 -61
- tensorcircuit/quantum.py +697 -133
- tensorcircuit/quditcircuit.py +733 -0
- tensorcircuit/quditgates.py +618 -0
- tensorcircuit/results/counts.py +45 -31
- tensorcircuit/shadows.py +1 -1
- tensorcircuit/simplify.py +3 -1
- tensorcircuit/stabilizercircuit.py +4 -2
- tensorcircuit/templates/blocks.py +2 -2
- tensorcircuit/templates/hamiltonians.py +29 -8
- tensorcircuit/templates/lattice.py +676 -335
- tensorcircuit/timeevol.py +896 -0
- {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/METADATA +50 -25
- tensorcircuit_nightly-1.4.0.dev20251103.dist-info/RECORD +96 -0
- {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/top_level.txt +0 -1
- tensorcircuit_nightly-1.3.0.dev20250728.dist-info/RECORD +0 -122
- 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 -1713
- 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 -318
- tests/test_gates.py +0 -156
- tests/test_hamiltonians.py +0 -159
- tests/test_interfaces.py +0 -557
- tests/test_keras.py +0 -160
- tests/test_lattice.py +0 -1666
- tests/test_miscs.py +0 -334
- 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 -379
- tests/test_shadows.py +0 -160
- tests/test_simplify.py +0 -46
- tests/test_stabilizer.py +0 -226
- tests/test_templates.py +0 -218
- tests/test_torchnn.py +0 -99
- tests/test_van.py +0 -102
- {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/WHEEL +0 -0
- {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/licenses/LICENSE +0 -0
tensorcircuit/fgs.py
CHANGED
|
@@ -28,7 +28,6 @@ def onehot_matrix(i: int, j: int, N: int) -> Tensor:
|
|
|
28
28
|
|
|
29
29
|
# TODO(@refraction-ray): efficiency benchmark with jit
|
|
30
30
|
# TODO(@refraction-ray): FGS mixed state support?
|
|
31
|
-
# TODO(@refraction-ray): overlap?
|
|
32
31
|
# TODO(@refraction-ray): fermionic logarithmic negativity
|
|
33
32
|
|
|
34
33
|
|
|
@@ -227,7 +226,7 @@ class FGSSimulator:
|
|
|
227
226
|
return self.alpha
|
|
228
227
|
|
|
229
228
|
def get_cmatrix(self, now_i: bool = True, now_j: bool = True) -> Tensor:
|
|
230
|
-
"""
|
|
229
|
+
r"""
|
|
231
230
|
Calculates the correlation matrix.
|
|
232
231
|
|
|
233
232
|
The correlation matrix is defined as :math:`C_{ij} = \langle c_i^\dagger c_j \rangle`.
|
|
@@ -509,7 +508,7 @@ class FGSSimulator:
|
|
|
509
508
|
|
|
510
509
|
@staticmethod
|
|
511
510
|
def hopping(chi: Tensor, i: int, j: int, L: int) -> Tensor:
|
|
512
|
-
"""
|
|
511
|
+
r"""
|
|
513
512
|
Constructs the hopping Hamiltonian between two sites.
|
|
514
513
|
|
|
515
514
|
The hopping Hamiltonian is given by :math:`\chi c_i^\dagger c_j + h.c.`.
|
|
@@ -550,7 +549,7 @@ class FGSSimulator:
|
|
|
550
549
|
|
|
551
550
|
@staticmethod
|
|
552
551
|
def chemical_potential(chi: Tensor, i: int, L: int) -> Tensor:
|
|
553
|
-
"""
|
|
552
|
+
r"""
|
|
554
553
|
Constructs the chemical potential Hamiltonian for a single site.
|
|
555
554
|
|
|
556
555
|
The chemical potential Hamiltonian is given by :math:`\chi c_i^\dagger c_i`.
|
|
@@ -572,7 +571,7 @@ class FGSSimulator:
|
|
|
572
571
|
|
|
573
572
|
@staticmethod
|
|
574
573
|
def sc_pairing(chi: Tensor, i: int, j: int, L: int) -> Tensor:
|
|
575
|
-
"""
|
|
574
|
+
r"""
|
|
576
575
|
Constructs the superconducting pairing Hamiltonian between two sites.
|
|
577
576
|
|
|
578
577
|
The superconducting pairing Hamiltonian is given by :math:`\chi c_i^\dagger c_j^\dagger + h.c.`.
|
|
@@ -637,7 +636,7 @@ class FGSSimulator:
|
|
|
637
636
|
self.evol_ihamiltonian(self.chemical_potential(chi, i, self.L))
|
|
638
637
|
|
|
639
638
|
def get_bogoliubov_uv(self) -> Tuple[Tensor, Tensor]:
|
|
640
|
-
"""
|
|
639
|
+
r"""
|
|
641
640
|
Returns the u and v matrices of the Bogoliubov transformation.
|
|
642
641
|
|
|
643
642
|
The Bogoliubov transformation is defined as:
|
tensorcircuit/gates.py
CHANGED
|
@@ -34,6 +34,12 @@ one_state = np.array([0.0, 1.0], dtype=npdtype)
|
|
|
34
34
|
plus_state = 1.0 / np.sqrt(2) * (zero_state + one_state)
|
|
35
35
|
minus_state = 1.0 / np.sqrt(2) * (zero_state - one_state)
|
|
36
36
|
|
|
37
|
+
# Common elements as np.ndarray objects
|
|
38
|
+
_i00 = np.array([[1.0, 0.0], [0.0, 0.0]])
|
|
39
|
+
_i01 = np.array([[0.0, 1.0], [0.0, 0.0]])
|
|
40
|
+
_i10 = np.array([[0.0, 0.0], [1.0, 0.0]])
|
|
41
|
+
_i11 = np.array([[0.0, 0.0], [0.0, 1.0]])
|
|
42
|
+
|
|
37
43
|
# Common single qubit gates as np.ndarray objects
|
|
38
44
|
_h_matrix = 1 / np.sqrt(2) * np.array([[1.0, 1.0], [1.0, -1.0]])
|
|
39
45
|
_i_matrix = np.array([[1.0, 0.0], [0.0, 1.0]])
|
|
@@ -229,7 +235,7 @@ def num_to_tensor(*num: Union[float, Tensor], dtype: Optional[str] = None) -> An
|
|
|
229
235
|
# TODO(@YHPeter): fix __doc__ for same function with different names
|
|
230
236
|
|
|
231
237
|
l = []
|
|
232
|
-
if
|
|
238
|
+
if dtype is None:
|
|
233
239
|
dtype = dtypestr
|
|
234
240
|
for n in num:
|
|
235
241
|
if not backend.is_tensor(n):
|
|
@@ -245,7 +251,7 @@ array_to_tensor = num_to_tensor
|
|
|
245
251
|
|
|
246
252
|
|
|
247
253
|
def gate_wrapper(m: Tensor, n: Optional[str] = None) -> Gate:
|
|
248
|
-
if
|
|
254
|
+
if n is None:
|
|
249
255
|
n = "unknowngate"
|
|
250
256
|
m = m.astype(npdtype)
|
|
251
257
|
return Gate(deepcopy(m), name=n)
|
|
@@ -255,7 +261,7 @@ class GateF:
|
|
|
255
261
|
def __init__(
|
|
256
262
|
self, m: Tensor, n: Optional[str] = None, ctrl: Optional[List[int]] = None
|
|
257
263
|
):
|
|
258
|
-
if
|
|
264
|
+
if n is None:
|
|
259
265
|
n = "unknowngate"
|
|
260
266
|
self.m = m
|
|
261
267
|
self.n = n
|
|
@@ -310,7 +316,7 @@ class GateF:
|
|
|
310
316
|
|
|
311
317
|
return Gate(cu, name="c" + self.n)
|
|
312
318
|
|
|
313
|
-
if
|
|
319
|
+
if self.ctrl is None:
|
|
314
320
|
ctrl = [1]
|
|
315
321
|
else:
|
|
316
322
|
ctrl = [1] + self.ctrl
|
|
@@ -330,7 +336,7 @@ class GateF:
|
|
|
330
336
|
# TODO(@refraction-ray): ctrl convention to be finally determined
|
|
331
337
|
return Gate(ocu, name="o" + self.n)
|
|
332
338
|
|
|
333
|
-
if
|
|
339
|
+
if self.ctrl is None:
|
|
334
340
|
ctrl = [0]
|
|
335
341
|
else:
|
|
336
342
|
ctrl = [0] + self.ctrl
|
|
@@ -349,7 +355,7 @@ class GateVF(GateF):
|
|
|
349
355
|
n: Optional[str] = None,
|
|
350
356
|
ctrl: Optional[List[int]] = None,
|
|
351
357
|
):
|
|
352
|
-
if
|
|
358
|
+
if n is None:
|
|
353
359
|
n = "unknowngate"
|
|
354
360
|
self.f = f
|
|
355
361
|
self.n = n
|
|
@@ -483,7 +489,7 @@ def phase_gate(theta: float = 0) -> Gate:
|
|
|
483
489
|
:rtype: Gate
|
|
484
490
|
"""
|
|
485
491
|
theta = array_to_tensor(theta)
|
|
486
|
-
i00, i11 = array_to_tensor(
|
|
492
|
+
i00, i11 = array_to_tensor(_i00, _i11)
|
|
487
493
|
unitary = i00 + backend.exp(1.0j * theta) * i11
|
|
488
494
|
return Gate(unitary)
|
|
489
495
|
|
|
@@ -512,7 +518,7 @@ def get_u_parameter(m: Tensor) -> Tuple[float, float, float]:
|
|
|
512
518
|
return theta, phi, lbd
|
|
513
519
|
|
|
514
520
|
|
|
515
|
-
def u_gate(theta: float = 0, phi: float = 0, lbd: float = 0) -> Gate:
|
|
521
|
+
def u_gate(theta: float = 0.0, phi: float = 0.0, lbd: float = 0.0) -> Gate:
|
|
516
522
|
r"""
|
|
517
523
|
IBMQ U gate following the converntion of OpenQASM3.0.
|
|
518
524
|
See `OpenQASM doc <https://openqasm.com/language/gates.html#built-in-gates>`_
|
|
@@ -533,12 +539,7 @@ def u_gate(theta: float = 0, phi: float = 0, lbd: float = 0) -> Gate:
|
|
|
533
539
|
:rtype: Gate
|
|
534
540
|
"""
|
|
535
541
|
theta, phi, lbd = array_to_tensor(theta, phi, lbd)
|
|
536
|
-
i00, i01, i10, i11 = array_to_tensor(
|
|
537
|
-
np.array([[1, 0], [0, 0]]),
|
|
538
|
-
np.array([[0, 1], [0, 0]]),
|
|
539
|
-
np.array([[0, 0], [1, 0]]),
|
|
540
|
-
np.array([[0, 0], [0, 1]]),
|
|
541
|
-
)
|
|
542
|
+
i00, i01, i10, i11 = array_to_tensor(_i00, _i01, _i10, _i11)
|
|
542
543
|
unitary = (
|
|
543
544
|
backend.cos(theta / 2) * i00
|
|
544
545
|
- backend.exp(1.0j * lbd) * backend.sin(theta / 2) * i01
|
|
@@ -548,7 +549,7 @@ def u_gate(theta: float = 0, phi: float = 0, lbd: float = 0) -> Gate:
|
|
|
548
549
|
return Gate(unitary)
|
|
549
550
|
|
|
550
551
|
|
|
551
|
-
def r_gate(theta: float = 0, alpha: float = 0, phi: float = 0) -> Gate:
|
|
552
|
+
def r_gate(theta: float = 0.0, alpha: float = 0.0, phi: float = 0.0) -> Gate:
|
|
552
553
|
r"""
|
|
553
554
|
General single qubit rotation gate
|
|
554
555
|
|
|
@@ -582,7 +583,7 @@ def r_gate(theta: float = 0, alpha: float = 0, phi: float = 0) -> Gate:
|
|
|
582
583
|
# r = r_gate
|
|
583
584
|
|
|
584
585
|
|
|
585
|
-
def rx_gate(theta: float = 0) -> Gate:
|
|
586
|
+
def rx_gate(theta: float = 0.0) -> Gate:
|
|
586
587
|
r"""
|
|
587
588
|
Rotation gate along :math:`x` axis.
|
|
588
589
|
|
|
@@ -603,7 +604,7 @@ def rx_gate(theta: float = 0) -> Gate:
|
|
|
603
604
|
# rx = rx_gate
|
|
604
605
|
|
|
605
606
|
|
|
606
|
-
def ry_gate(theta: float = 0) -> Gate:
|
|
607
|
+
def ry_gate(theta: float = 0.0) -> Gate:
|
|
607
608
|
r"""
|
|
608
609
|
Rotation gate along :math:`y` axis.
|
|
609
610
|
|
|
@@ -624,7 +625,7 @@ def ry_gate(theta: float = 0) -> Gate:
|
|
|
624
625
|
# ry = ry_gate
|
|
625
626
|
|
|
626
627
|
|
|
627
|
-
def rz_gate(theta: float = 0) -> Gate:
|
|
628
|
+
def rz_gate(theta: float = 0.0) -> Gate:
|
|
628
629
|
r"""
|
|
629
630
|
Rotation gate along :math:`z` axis.
|
|
630
631
|
|
|
@@ -645,7 +646,7 @@ def rz_gate(theta: float = 0) -> Gate:
|
|
|
645
646
|
# rz = rz_gate
|
|
646
647
|
|
|
647
648
|
|
|
648
|
-
def rgate_theoretical(theta: float = 0, alpha: float = 0, phi: float = 0) -> Gate:
|
|
649
|
+
def rgate_theoretical(theta: float = 0.0, alpha: float = 0.0, phi: float = 0.0) -> Gate:
|
|
649
650
|
r"""
|
|
650
651
|
Rotation gate implemented by matrix exponential. The output is the same as `rgate`.
|
|
651
652
|
|
|
@@ -723,7 +724,7 @@ def iswap_gate(theta: float = 1.0) -> Gate:
|
|
|
723
724
|
# iswap = iswap_gate
|
|
724
725
|
|
|
725
726
|
|
|
726
|
-
def cr_gate(theta: float = 0, alpha: float = 0, phi: float = 0) -> Gate:
|
|
727
|
+
def cr_gate(theta: float = 0.0, alpha: float = 0.0, phi: float = 0.0) -> Gate:
|
|
727
728
|
r"""
|
|
728
729
|
Controlled rotation gate. When the control qubit is 1, `rgate` is applied to the target qubit.
|
|
729
730
|
|
|
@@ -775,7 +776,7 @@ def random_two_qubit_gate() -> Gate:
|
|
|
775
776
|
return Gate(deepcopy(unitary), name="R2Q")
|
|
776
777
|
|
|
777
778
|
|
|
778
|
-
def any_gate(unitary: Tensor, name: str = "any") -> Gate:
|
|
779
|
+
def any_gate(unitary: Tensor, name: str = "any", dim: Optional[int] = None) -> Gate:
|
|
779
780
|
"""
|
|
780
781
|
Note one should provide the gate with properly reshaped.
|
|
781
782
|
|
|
@@ -783,6 +784,8 @@ def any_gate(unitary: Tensor, name: str = "any") -> Gate:
|
|
|
783
784
|
:type unitary: Tensor
|
|
784
785
|
:param name: The name of the gate.
|
|
785
786
|
:type name: str
|
|
787
|
+
:param dim: The dimension of the gate.
|
|
788
|
+
:type dim: int
|
|
786
789
|
:return: the resulted gate
|
|
787
790
|
:rtype: Gate
|
|
788
791
|
"""
|
|
@@ -791,7 +794,10 @@ def any_gate(unitary: Tensor, name: str = "any") -> Gate:
|
|
|
791
794
|
unitary.tensor = backend.cast(unitary.tensor, dtypestr)
|
|
792
795
|
return unitary
|
|
793
796
|
unitary = backend.cast(unitary, dtypestr)
|
|
794
|
-
|
|
797
|
+
if dim is None or dim == 2:
|
|
798
|
+
unitary = backend.reshape2(unitary)
|
|
799
|
+
else:
|
|
800
|
+
unitary = backend.reshaped(unitary, dim)
|
|
795
801
|
# nleg = int(np.log2(backend.sizen(unitary)))
|
|
796
802
|
# unitary = backend.reshape(unitary, [2 for _ in range(nleg)])
|
|
797
803
|
return Gate(unitary, name=name)
|
|
@@ -864,6 +870,43 @@ def exponential_gate_unity(
|
|
|
864
870
|
return Gate(mat, name="exp1-" + name)
|
|
865
871
|
|
|
866
872
|
|
|
873
|
+
def su4_gate(theta: Tensor, name: str = "su(4)") -> Gate:
|
|
874
|
+
r"""
|
|
875
|
+
Two-qubit general SU(4) gate.
|
|
876
|
+
|
|
877
|
+
:param theta: the angle tensor (15 components) of the gate.
|
|
878
|
+
:type theta: Tensor
|
|
879
|
+
:param name: the name of the gate.
|
|
880
|
+
:type name: str
|
|
881
|
+
:return: a gate object.
|
|
882
|
+
:rtype: Gate
|
|
883
|
+
"""
|
|
884
|
+
theta = num_to_tensor(theta)
|
|
885
|
+
pauli_ops = array_to_tensor(
|
|
886
|
+
_ix_matrix,
|
|
887
|
+
_iy_matrix,
|
|
888
|
+
_iz_matrix,
|
|
889
|
+
_xi_matrix,
|
|
890
|
+
_xx_matrix,
|
|
891
|
+
_xy_matrix,
|
|
892
|
+
_xz_matrix,
|
|
893
|
+
_yi_matrix,
|
|
894
|
+
_yx_matrix,
|
|
895
|
+
_yy_matrix,
|
|
896
|
+
_yz_matrix,
|
|
897
|
+
_zi_matrix,
|
|
898
|
+
_zx_matrix,
|
|
899
|
+
_zy_matrix,
|
|
900
|
+
_zz_matrix,
|
|
901
|
+
)
|
|
902
|
+
generator = backend.sum(
|
|
903
|
+
backend.stack([theta[i] * pauli_ops[i] for i in range(15)]), axis=0
|
|
904
|
+
)
|
|
905
|
+
mat = backend.expm(-1j * generator)
|
|
906
|
+
mat = backend.reshape2(mat)
|
|
907
|
+
return Gate(mat, name=name)
|
|
908
|
+
|
|
909
|
+
|
|
867
910
|
exp1_gate = exponential_gate_unity
|
|
868
911
|
# exp1 = exponential_gate_unity
|
|
869
912
|
rzz_gate = partial(exp1_gate, unitary=_zz_matrix, half=True)
|
|
@@ -968,6 +1011,7 @@ def meta_vgate() -> None:
|
|
|
968
1011
|
"rzz",
|
|
969
1012
|
"rxx",
|
|
970
1013
|
"ryy",
|
|
1014
|
+
"su4",
|
|
971
1015
|
]:
|
|
972
1016
|
for funcname in [f, f + "gate"]:
|
|
973
1017
|
setattr(thismodule, funcname, GateVF(getattr(thismodule, f + "_gate"), f))
|
tensorcircuit/keras.py
CHANGED
|
@@ -24,7 +24,7 @@ class QuantumLayer(Layer): # type: ignore
|
|
|
24
24
|
initializer: Union[Text, Sequence[Text]] = "glorot_uniform",
|
|
25
25
|
constraint: Optional[Union[Text, Sequence[Text]]] = None,
|
|
26
26
|
regularizer: Optional[Union[Text, Sequence[Text]]] = None,
|
|
27
|
-
**kwargs: Any
|
|
27
|
+
**kwargs: Any,
|
|
28
28
|
) -> None:
|
|
29
29
|
"""
|
|
30
30
|
`QuantumLayer` wraps the quantum function `f` as a `keras.Layer`
|
|
@@ -103,7 +103,7 @@ class QuantumLayer(Layer): # type: ignore
|
|
|
103
103
|
inputs: tf.Tensor,
|
|
104
104
|
training: Optional[bool] = None,
|
|
105
105
|
mask: Optional[tf.Tensor] = None,
|
|
106
|
-
**kwargs: Any
|
|
106
|
+
**kwargs: Any,
|
|
107
107
|
) -> tf.Tensor:
|
|
108
108
|
# input_shape = list(inputs.shape)
|
|
109
109
|
# inputs = tf.reshape(inputs, (-1, input_shape[-1]))
|
|
@@ -154,7 +154,7 @@ class HardwareLayer(QuantumLayer):
|
|
|
154
154
|
inputs: tf.Tensor,
|
|
155
155
|
training: Optional[bool] = None,
|
|
156
156
|
mask: Optional[tf.Tensor] = None,
|
|
157
|
-
**kwargs: Any
|
|
157
|
+
**kwargs: Any,
|
|
158
158
|
) -> tf.Tensor:
|
|
159
159
|
if inputs is None: # not possible
|
|
160
160
|
result = self.f(*self.pqc_weights, **kwargs)
|
tensorcircuit/mpscircuit.py
CHANGED
|
@@ -4,21 +4,25 @@ Quantum circuit: MPS state simulator
|
|
|
4
4
|
|
|
5
5
|
# pylint: disable=invalid-name
|
|
6
6
|
|
|
7
|
-
from functools import reduce
|
|
7
|
+
from functools import reduce, partial
|
|
8
8
|
from typing import Any, List, Optional, Sequence, Tuple, Dict, Union
|
|
9
9
|
from copy import copy
|
|
10
|
+
import logging
|
|
11
|
+
import types
|
|
10
12
|
|
|
11
13
|
import numpy as np
|
|
12
14
|
import tensornetwork as tn
|
|
13
|
-
from tensorcircuit.quantum import QuOperator, QuVector
|
|
14
15
|
|
|
15
16
|
from . import gates
|
|
16
17
|
from .cons import backend, npdtype, contractor, rdtypestr, dtypestr
|
|
18
|
+
from .quantum import QuOperator, QuVector, extract_tensors_from_qop, _decode_basis_label
|
|
17
19
|
from .mps_base import FiniteMPS
|
|
18
20
|
from .abstractcircuit import AbstractCircuit
|
|
21
|
+
from .utils import arg_alias
|
|
19
22
|
|
|
20
23
|
Gate = gates.Gate
|
|
21
24
|
Tensor = Any
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
def split_tensor(
|
|
@@ -77,6 +81,10 @@ class MPSCircuit(AbstractCircuit):
|
|
|
77
81
|
|
|
78
82
|
is_mps = True
|
|
79
83
|
|
|
84
|
+
@partial(
|
|
85
|
+
arg_alias,
|
|
86
|
+
alias_dict={"wavefunction": ["inputs"]},
|
|
87
|
+
)
|
|
80
88
|
def __init__(
|
|
81
89
|
self,
|
|
82
90
|
nqubits: int,
|
|
@@ -84,12 +92,16 @@ class MPSCircuit(AbstractCircuit):
|
|
|
84
92
|
tensors: Optional[Sequence[Tensor]] = None,
|
|
85
93
|
wavefunction: Optional[Union[QuVector, Tensor]] = None,
|
|
86
94
|
split: Optional[Dict[str, Any]] = None,
|
|
95
|
+
dim: Optional[int] = None,
|
|
87
96
|
) -> None:
|
|
88
97
|
"""
|
|
89
98
|
MPSCircuit object based on state simulator.
|
|
99
|
+
Do not use this class with d!=2 directly
|
|
90
100
|
|
|
91
101
|
:param nqubits: The number of qubits in the circuit.
|
|
92
102
|
:type nqubits: int
|
|
103
|
+
:param dim: The local Hilbert space dimension per site. Qudit is supported for 2 <= d <= 36.
|
|
104
|
+
:type dim: If None, the dimension of the circuit will be `2`, which is a qubit system.
|
|
93
105
|
:param center_position: The center position of MPS, default to 0
|
|
94
106
|
:type center_position: int, optional
|
|
95
107
|
:param tensors: If not None, the initial state of the circuit is taken as ``tensors``
|
|
@@ -102,6 +114,7 @@ class MPSCircuit(AbstractCircuit):
|
|
|
102
114
|
:param split: Split rules
|
|
103
115
|
:type split: Any
|
|
104
116
|
"""
|
|
117
|
+
self._d = 2 if dim is None else dim
|
|
105
118
|
self.circuit_param = {
|
|
106
119
|
"nqubits": nqubits,
|
|
107
120
|
"center_position": center_position,
|
|
@@ -118,8 +131,21 @@ class MPSCircuit(AbstractCircuit):
|
|
|
118
131
|
), "tensors and wavefunction cannot be used at input simutaneously"
|
|
119
132
|
# TODO(@SUSYUSTC): find better way to address QuVector
|
|
120
133
|
if isinstance(wavefunction, QuVector):
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
try:
|
|
135
|
+
nodes, is_mps, _ = extract_tensors_from_qop(wavefunction)
|
|
136
|
+
if not is_mps:
|
|
137
|
+
raise ValueError("wavefunction is not a valid MPS")
|
|
138
|
+
tensors = [node.tensor for node in nodes]
|
|
139
|
+
except ValueError as e:
|
|
140
|
+
logger.warning(repr(e))
|
|
141
|
+
wavefunction = wavefunction.eval()
|
|
142
|
+
tensors = self.wavefunction_to_tensors(
|
|
143
|
+
wavefunction, split=self.split
|
|
144
|
+
)
|
|
145
|
+
else: # full wavefunction
|
|
146
|
+
tensors = self.wavefunction_to_tensors(
|
|
147
|
+
wavefunction, dim_phys=self._d, split=self.split
|
|
148
|
+
)
|
|
123
149
|
assert len(tensors) == nqubits
|
|
124
150
|
self._mps = FiniteMPS(tensors, canonicalize=False)
|
|
125
151
|
self._mps.center_position = 0
|
|
@@ -133,8 +159,13 @@ class MPSCircuit(AbstractCircuit):
|
|
|
133
159
|
self._mps = FiniteMPS(tensors, canonicalize=True, center_position=0)
|
|
134
160
|
else:
|
|
135
161
|
tensors = [
|
|
136
|
-
np.
|
|
137
|
-
|
|
162
|
+
np.concatenate(
|
|
163
|
+
[
|
|
164
|
+
np.array([1.0], dtype=npdtype),
|
|
165
|
+
np.zeros((self._d - 1,), dtype=npdtype),
|
|
166
|
+
]
|
|
167
|
+
)[None, :, None]
|
|
168
|
+
for _ in range(nqubits)
|
|
138
169
|
]
|
|
139
170
|
self._mps = FiniteMPS(tensors, canonicalize=False)
|
|
140
171
|
if center_position is not None:
|
|
@@ -352,42 +383,51 @@ class MPSCircuit(AbstractCircuit):
|
|
|
352
383
|
# b
|
|
353
384
|
|
|
354
385
|
# index must be ordered
|
|
355
|
-
|
|
356
|
-
|
|
386
|
+
if len(index) == 0:
|
|
387
|
+
raise ValueError("`index` must contain at least one site.")
|
|
388
|
+
if not all(index[i] < index[i + 1] for i in range(len(index) - 1)):
|
|
389
|
+
raise ValueError("`index` must be strictly increasing.")
|
|
390
|
+
|
|
391
|
+
index_left = int(np.min(index))
|
|
357
392
|
if isinstance(gate, tn.Node):
|
|
358
393
|
gate = backend.copy(gate.tensor)
|
|
359
|
-
|
|
394
|
+
|
|
360
395
|
nindex = len(index)
|
|
396
|
+
in_dims = tuple(backend.shape_tuple(gate))[:nindex]
|
|
397
|
+
dim = int(in_dims[0])
|
|
398
|
+
dim_phys_mpo = dim * dim
|
|
399
|
+
gate = backend.reshape(gate, (dim,) * nindex + (dim,) * nindex)
|
|
361
400
|
# transform gate from (in1, in2, ..., out1, out2 ...) to
|
|
362
401
|
# (in1, out1, in2, out2, ...)
|
|
363
|
-
order = tuple(np.arange(2 * nindex).reshape(
|
|
364
|
-
|
|
365
|
-
gate = backend.reshape(backend.transpose(gate, order), shape)
|
|
366
|
-
argsort = np.argsort(index)
|
|
402
|
+
order = tuple(np.arange(2 * nindex).reshape(2, nindex).T.flatten().tolist())
|
|
403
|
+
gate = backend.transpose(gate, order)
|
|
367
404
|
# reorder the gate according to the site positions
|
|
368
|
-
gate = backend.
|
|
369
|
-
index = index[argsort] # type: ignore
|
|
405
|
+
gate = backend.reshape(gate, (dim_phys_mpo,) * nindex)
|
|
370
406
|
# split the gate into tensors assuming they are adjacent
|
|
371
|
-
main_tensors = cls.wavefunction_to_tensors(
|
|
407
|
+
main_tensors = cls.wavefunction_to_tensors(
|
|
408
|
+
gate, dim_phys=dim_phys_mpo, norm=False
|
|
409
|
+
)
|
|
372
410
|
# each tensor is in shape of (i, a, b, j)
|
|
373
|
-
tensors = []
|
|
374
|
-
previous_i = None
|
|
375
|
-
|
|
376
|
-
|
|
411
|
+
tensors: list[Tensor] = []
|
|
412
|
+
previous_i: Optional[int] = None
|
|
413
|
+
index_arr = np.array(index, dtype=int) - index_left
|
|
414
|
+
|
|
415
|
+
for i, main_tensor in zip(index_arr, main_tensors):
|
|
377
416
|
if previous_i is not None:
|
|
378
|
-
for
|
|
379
|
-
bond_dim = tensors[-1]
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
.reshape((bond_dim, 2, bond_dim, 2))
|
|
383
|
-
.transpose((0, 1, 3, 2))
|
|
384
|
-
.astype(dtypestr)
|
|
417
|
+
for _gap_site in range(int(previous_i) + 1, int(i)):
|
|
418
|
+
bond_dim = int(backend.shape_tuple(tensors[-1])[-1])
|
|
419
|
+
eye2d = backend.eye(
|
|
420
|
+
bond_dim * dim, dtype=backend.dtype(tensors[-1])
|
|
385
421
|
)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
422
|
+
I4 = backend.reshape(eye2d, (bond_dim, dim, bond_dim, dim))
|
|
423
|
+
I4 = backend.transpose(I4, (0, 1, 3, 2))
|
|
424
|
+
tensors.append(I4)
|
|
425
|
+
|
|
426
|
+
nleft, _, nright = backend.shape_tuple(main_tensor)
|
|
427
|
+
tensor = backend.reshape(main_tensor, (int(nleft), dim, dim, int(nright)))
|
|
389
428
|
tensors.append(tensor)
|
|
390
|
-
previous_i = i
|
|
429
|
+
previous_i = int(i)
|
|
430
|
+
|
|
391
431
|
return tensors, index_left
|
|
392
432
|
|
|
393
433
|
@classmethod
|
|
@@ -430,15 +470,15 @@ class MPSCircuit(AbstractCircuit):
|
|
|
430
470
|
"""
|
|
431
471
|
if split is None:
|
|
432
472
|
split = {}
|
|
433
|
-
ni = tensor_left.shape[0]
|
|
434
|
-
nk = tensor_right.shape[-1]
|
|
473
|
+
ni, di = tensor_left.shape[0], tensor_right.shape[1]
|
|
474
|
+
nk, dk = tensor_right.shape[-1], tensor_right.shape[-2]
|
|
435
475
|
T = backend.einsum("iaj,jbk->iabk", tensor_left, tensor_right)
|
|
436
|
-
T = backend.reshape(T, (ni *
|
|
476
|
+
T = backend.reshape(T, (ni * di, nk * dk))
|
|
437
477
|
new_tensor_left, new_tensor_right = split_tensor(
|
|
438
478
|
T, center_left=center_left, split=split
|
|
439
479
|
)
|
|
440
|
-
new_tensor_left = backend.reshape(new_tensor_left, (ni,
|
|
441
|
-
new_tensor_right = backend.reshape(new_tensor_right, (-1,
|
|
480
|
+
new_tensor_left = backend.reshape(new_tensor_left, (ni, di, -1))
|
|
481
|
+
new_tensor_right = backend.reshape(new_tensor_right, (-1, dk, nk))
|
|
442
482
|
return new_tensor_left, new_tensor_right
|
|
443
483
|
|
|
444
484
|
def reduce_dimension(
|
|
@@ -532,10 +572,11 @@ class MPSCircuit(AbstractCircuit):
|
|
|
532
572
|
for i, idx in zip(i_list, idx_list):
|
|
533
573
|
O = tensors[i]
|
|
534
574
|
T = self._mps.tensors[idx]
|
|
535
|
-
ni,
|
|
575
|
+
ni, d_in, _, nj = O.shape
|
|
536
576
|
nk, _, nl = T.shape
|
|
537
577
|
OT = backend.einsum("iabj,kbl->ikajl", O, T)
|
|
538
|
-
OT = backend.reshape(OT, (ni * nk,
|
|
578
|
+
OT = backend.reshape(OT, (ni * nk, d_in, nj * nl))
|
|
579
|
+
|
|
539
580
|
self._mps.tensors[idx] = OT
|
|
540
581
|
|
|
541
582
|
# canonicalize
|
|
@@ -642,8 +683,7 @@ class MPSCircuit(AbstractCircuit):
|
|
|
642
683
|
:type keep: int, optional
|
|
643
684
|
"""
|
|
644
685
|
# normalization not guaranteed
|
|
645
|
-
|
|
646
|
-
gate = backend.zeros((2, 2), dtype=dtypestr)
|
|
686
|
+
gate = backend.zeros((self._d, self._d), dtype=dtypestr)
|
|
647
687
|
gate = backend.scatter(
|
|
648
688
|
gate,
|
|
649
689
|
backend.convert_to_tensor([[keep, keep]]),
|
|
@@ -674,7 +714,7 @@ class MPSCircuit(AbstractCircuit):
|
|
|
674
714
|
def wavefunction_to_tensors(
|
|
675
715
|
cls,
|
|
676
716
|
wavefunction: Tensor,
|
|
677
|
-
dim_phys: int =
|
|
717
|
+
dim_phys: Optional[int] = None,
|
|
678
718
|
norm: bool = True,
|
|
679
719
|
split: Optional[Dict[str, Any]] = None,
|
|
680
720
|
) -> List[Tensor]:
|
|
@@ -692,6 +732,7 @@ class MPSCircuit(AbstractCircuit):
|
|
|
692
732
|
:return: The tensors
|
|
693
733
|
:rtype: List[Tensor]
|
|
694
734
|
"""
|
|
735
|
+
dim_phys = dim_phys if dim_phys is not None else 2
|
|
695
736
|
if split is None:
|
|
696
737
|
split = {}
|
|
697
738
|
wavefunction = backend.reshape(wavefunction, (-1, 1))
|
|
@@ -750,10 +791,16 @@ class MPSCircuit(AbstractCircuit):
|
|
|
750
791
|
for key in vars(self):
|
|
751
792
|
if key == "_mps":
|
|
752
793
|
continue
|
|
753
|
-
|
|
754
|
-
|
|
794
|
+
val = info[key]
|
|
795
|
+
if backend.is_tensor(val):
|
|
796
|
+
copied_value = backend.copy(val)
|
|
797
|
+
elif isinstance(val, types.ModuleType):
|
|
798
|
+
copied_value = val
|
|
755
799
|
else:
|
|
756
|
-
|
|
800
|
+
try:
|
|
801
|
+
copied_value = copy(val)
|
|
802
|
+
except TypeError:
|
|
803
|
+
copied_value = val
|
|
757
804
|
setattr(result, key, copied_value)
|
|
758
805
|
return result
|
|
759
806
|
|
|
@@ -797,7 +844,8 @@ class MPSCircuit(AbstractCircuit):
|
|
|
797
844
|
|
|
798
845
|
def amplitude(self, l: str) -> Tensor:
|
|
799
846
|
assert len(l) == self._nqubits
|
|
800
|
-
|
|
847
|
+
idx_list = _decode_basis_label(l, n=self._nqubits, dim=self._d)
|
|
848
|
+
tensors = [self._mps.tensors[i][:, idx, :] for i, idx in enumerate(idx_list)]
|
|
801
849
|
return reduce(backend.matmul, tensors)[0, 0]
|
|
802
850
|
|
|
803
851
|
def proj_with_mps(self, other: "MPSCircuit", conj: bool = True) -> Tensor:
|
|
@@ -855,6 +903,7 @@ class MPSCircuit(AbstractCircuit):
|
|
|
855
903
|
|
|
856
904
|
mps = self.__class__(
|
|
857
905
|
nqubits,
|
|
906
|
+
dim=self._d,
|
|
858
907
|
tensors=tensors,
|
|
859
908
|
center_position=center_position,
|
|
860
909
|
split=self.split.copy(),
|
|
@@ -982,36 +1031,35 @@ class MPSCircuit(AbstractCircuit):
|
|
|
982
1031
|
# set the center to the left side, then gradually move to the right and do measurement at sites
|
|
983
1032
|
"""
|
|
984
1033
|
mps = self.copy()
|
|
985
|
-
up = backend.convert_to_tensor(np.array([1, 0]).astype(dtypestr))
|
|
986
|
-
down = backend.convert_to_tensor(np.array([0, 1]).astype(dtypestr))
|
|
987
1034
|
|
|
988
1035
|
p = 1.0
|
|
989
1036
|
p = backend.convert_to_tensor(p)
|
|
990
1037
|
p = backend.cast(p, dtype=rdtypestr)
|
|
991
|
-
sample = []
|
|
1038
|
+
sample: Tensor = []
|
|
992
1039
|
for k, site in enumerate(index):
|
|
993
1040
|
mps.position(site)
|
|
994
|
-
# do measurement
|
|
995
1041
|
tensor = mps._mps.tensors[site]
|
|
996
1042
|
ps = backend.real(
|
|
997
1043
|
backend.einsum("iaj,iaj->a", tensor, backend.conj(tensor))
|
|
998
1044
|
)
|
|
999
1045
|
ps /= backend.sum(ps)
|
|
1000
|
-
pu = ps[0]
|
|
1001
1046
|
if status is None:
|
|
1002
|
-
|
|
1047
|
+
outcome = backend.implicit_randc(
|
|
1048
|
+
self._d, shape=1, p=backend.cast(ps, rdtypestr)
|
|
1049
|
+
)[0]
|
|
1003
1050
|
else:
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
m =
|
|
1051
|
+
one_r = backend.cast(backend.convert_to_tensor(1.0), rdtypestr)
|
|
1052
|
+
st = backend.cast(status[k : k + 1], rdtypestr)
|
|
1053
|
+
ind = backend.probability_sample(
|
|
1054
|
+
shots=1, p=backend.cast(ps, rdtypestr), status=one_r - st
|
|
1055
|
+
)
|
|
1056
|
+
outcome = backend.cast(ind[0], "int32")
|
|
1057
|
+
|
|
1058
|
+
p = p * ps[outcome]
|
|
1059
|
+
basis = backend.convert_to_tensor(np.eye(self._d).astype(dtypestr))
|
|
1060
|
+
m = basis[outcome]
|
|
1014
1061
|
mps._mps.tensors[site] = backend.einsum("iaj,a->ij", tensor, m)[:, None, :]
|
|
1062
|
+
sample.append(outcome)
|
|
1015
1063
|
sample = backend.stack(sample)
|
|
1016
1064
|
sample = backend.real(sample)
|
|
1017
1065
|
if with_prob:
|