tensorcircuit-nightly 1.3.0.dev20250728__py3-none-any.whl → 1.4.0.dev20251103__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (72) hide show
  1. tensorcircuit/__init__.py +5 -1
  2. tensorcircuit/abstractcircuit.py +4 -0
  3. tensorcircuit/analogcircuit.py +413 -0
  4. tensorcircuit/applications/layers.py +1 -1
  5. tensorcircuit/applications/van.py +1 -1
  6. tensorcircuit/backends/abstract_backend.py +312 -5
  7. tensorcircuit/backends/cupy_backend.py +3 -1
  8. tensorcircuit/backends/jax_backend.py +92 -3
  9. tensorcircuit/backends/jax_ops.py +108 -0
  10. tensorcircuit/backends/numpy_backend.py +49 -3
  11. tensorcircuit/backends/pytorch_backend.py +92 -3
  12. tensorcircuit/backends/tensorflow_backend.py +102 -3
  13. tensorcircuit/basecircuit.py +123 -82
  14. tensorcircuit/circuit.py +67 -57
  15. tensorcircuit/cloud/local.py +1 -1
  16. tensorcircuit/cloud/quafu_provider.py +1 -1
  17. tensorcircuit/cloud/tencent.py +1 -1
  18. tensorcircuit/compiler/simple_compiler.py +2 -2
  19. tensorcircuit/cons.py +1 -0
  20. tensorcircuit/densitymatrix.py +16 -11
  21. tensorcircuit/experimental.py +7 -152
  22. tensorcircuit/fgs.py +5 -6
  23. tensorcircuit/gates.py +66 -22
  24. tensorcircuit/keras.py +3 -3
  25. tensorcircuit/mpscircuit.py +109 -61
  26. tensorcircuit/quantum.py +697 -133
  27. tensorcircuit/quditcircuit.py +733 -0
  28. tensorcircuit/quditgates.py +618 -0
  29. tensorcircuit/results/counts.py +45 -31
  30. tensorcircuit/shadows.py +1 -1
  31. tensorcircuit/simplify.py +3 -1
  32. tensorcircuit/stabilizercircuit.py +4 -2
  33. tensorcircuit/templates/blocks.py +2 -2
  34. tensorcircuit/templates/hamiltonians.py +29 -8
  35. tensorcircuit/templates/lattice.py +676 -335
  36. tensorcircuit/timeevol.py +896 -0
  37. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/METADATA +50 -25
  38. tensorcircuit_nightly-1.4.0.dev20251103.dist-info/RECORD +96 -0
  39. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/top_level.txt +0 -1
  40. tensorcircuit_nightly-1.3.0.dev20250728.dist-info/RECORD +0 -122
  41. tests/__init__.py +0 -0
  42. tests/conftest.py +0 -67
  43. tests/test_backends.py +0 -1035
  44. tests/test_calibrating.py +0 -149
  45. tests/test_channels.py +0 -409
  46. tests/test_circuit.py +0 -1713
  47. tests/test_cloud.py +0 -219
  48. tests/test_compiler.py +0 -147
  49. tests/test_dmcircuit.py +0 -555
  50. tests/test_ensemble.py +0 -72
  51. tests/test_fgs.py +0 -318
  52. tests/test_gates.py +0 -156
  53. tests/test_hamiltonians.py +0 -159
  54. tests/test_interfaces.py +0 -557
  55. tests/test_keras.py +0 -160
  56. tests/test_lattice.py +0 -1666
  57. tests/test_miscs.py +0 -334
  58. tests/test_mpscircuit.py +0 -341
  59. tests/test_noisemodel.py +0 -156
  60. tests/test_qaoa.py +0 -86
  61. tests/test_qem.py +0 -152
  62. tests/test_quantum.py +0 -549
  63. tests/test_quantum_attr.py +0 -42
  64. tests/test_results.py +0 -379
  65. tests/test_shadows.py +0 -160
  66. tests/test_simplify.py +0 -46
  67. tests/test_stabilizer.py +0 -226
  68. tests/test_templates.py +0 -218
  69. tests/test_torchnn.py +0 -99
  70. tests/test_van.py +0 -102
  71. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/WHEEL +0 -0
  72. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/licenses/LICENSE +0 -0
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 not dtype:
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 not n:
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 not n:
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 not self.ctrl:
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 not self.ctrl:
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 not n:
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(np.array([[1, 0], [0, 0]]), np.array([[0, 0], [0, 1]]))
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
- unitary = backend.reshape2(unitary)
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)
@@ -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
- wavefunction = wavefunction.eval()
122
- tensors = self.wavefunction_to_tensors(wavefunction, split=self.split)
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.array([1.0, 0.0], dtype=npdtype)[None, :, None]
137
- for i in range(nqubits)
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
- assert np.all(np.diff(index) > 0)
356
- index_left = np.min(index)
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
- index = np.array(index) - index_left
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((2, nindex)).T.flatten())
364
- shape = (4,) * nindex
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.transpose(gate, tuple(argsort))
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(gate, dim_phys=4, norm=False)
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
- for i, main_tensor in zip(index, main_tensors):
376
- # insert identites in the middle
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 _ in range(previous_i + 1, i):
379
- bond_dim = tensors[-1].shape[-1]
380
- I = (
381
- np.eye(bond_dim * 2)
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
- tensors.append(backend.convert_to_tensor(I))
387
- nleft, _, nright = main_tensor.shape
388
- tensor = backend.reshape(main_tensor, (nleft, 2, 2, nright))
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 * 2, nk * 2))
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, 2, -1))
441
- new_tensor_right = backend.reshape(new_tensor_right, (-1, 2, nk))
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, _, _, nj = O.shape
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, 2, nj * nl))
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
- assert keep in [0, 1]
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 = 2,
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
- if backend.is_tensor(info[key]):
754
- copied_value = backend.copy(info[key])
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
- copied_value = copy(info[key])
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
- tensors = [self._mps.tensors[i][:, int(s), :] for i, s in enumerate(l)]
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
- r = backend.implicit_randu()[0]
1047
+ outcome = backend.implicit_randc(
1048
+ self._d, shape=1, p=backend.cast(ps, rdtypestr)
1049
+ )[0]
1003
1050
  else:
1004
- r = status[k]
1005
- r = backend.real(backend.cast(r, dtypestr))
1006
- eps = 0.31415926 * 1e-12
1007
- sign = backend.sign(r - pu + eps) / 2 + 0.5 # in case status is exactly 0.5
1008
- sign = backend.convert_to_tensor(sign)
1009
- sign = backend.cast(sign, dtype=rdtypestr)
1010
- sign_complex = backend.cast(sign, dtypestr)
1011
- sample.append(sign_complex)
1012
- p = p * (pu * (-1) ** sign + sign)
1013
- m = (1 - sign_complex) * up + sign_complex * down
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: