tensorcircuit-nightly 1.3.0.dev20250902__py3-none-any.whl → 1.3.0.dev20250904__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.

@@ -8,13 +8,14 @@ from functools import reduce, partial
8
8
  from typing import Any, List, Optional, Sequence, Tuple, Dict, Union
9
9
  from copy import copy
10
10
  import logging
11
+ import types
11
12
 
12
13
  import numpy as np
13
14
  import tensornetwork as tn
14
15
 
15
16
  from . import gates
16
17
  from .cons import backend, npdtype, contractor, rdtypestr, dtypestr
17
- from .quantum import QuOperator, QuVector, extract_tensors_from_qop
18
+ from .quantum import QuOperator, QuVector, extract_tensors_from_qop, _decode_basis_label
18
19
  from .mps_base import FiniteMPS
19
20
  from .abstractcircuit import AbstractCircuit
20
21
  from .utils import arg_alias
@@ -91,12 +92,16 @@ class MPSCircuit(AbstractCircuit):
91
92
  tensors: Optional[Sequence[Tensor]] = None,
92
93
  wavefunction: Optional[Union[QuVector, Tensor]] = None,
93
94
  split: Optional[Dict[str, Any]] = None,
95
+ dim: Optional[int] = None,
94
96
  ) -> None:
95
97
  """
96
98
  MPSCircuit object based on state simulator.
99
+ Do not use this class with d!=2 directly
97
100
 
98
101
  :param nqubits: The number of qubits in the circuit.
99
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.
100
105
  :param center_position: The center position of MPS, default to 0
101
106
  :type center_position: int, optional
102
107
  :param tensors: If not None, the initial state of the circuit is taken as ``tensors``
@@ -109,6 +114,7 @@ class MPSCircuit(AbstractCircuit):
109
114
  :param split: Split rules
110
115
  :type split: Any
111
116
  """
117
+ self._d = 2 if dim is None else dim
112
118
  self.circuit_param = {
113
119
  "nqubits": nqubits,
114
120
  "center_position": center_position,
@@ -137,7 +143,9 @@ class MPSCircuit(AbstractCircuit):
137
143
  wavefunction, split=self.split
138
144
  )
139
145
  else: # full wavefunction
140
- tensors = self.wavefunction_to_tensors(wavefunction, split=self.split)
146
+ tensors = self.wavefunction_to_tensors(
147
+ wavefunction, dim_phys=self._d, split=self.split
148
+ )
141
149
  assert len(tensors) == nqubits
142
150
  self._mps = FiniteMPS(tensors, canonicalize=False)
143
151
  self._mps.center_position = 0
@@ -151,8 +159,13 @@ class MPSCircuit(AbstractCircuit):
151
159
  self._mps = FiniteMPS(tensors, canonicalize=True, center_position=0)
152
160
  else:
153
161
  tensors = [
154
- np.array([1.0, 0.0], dtype=npdtype)[None, :, None]
155
- 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)
156
169
  ]
157
170
  self._mps = FiniteMPS(tensors, canonicalize=False)
158
171
  if center_position is not None:
@@ -370,42 +383,51 @@ class MPSCircuit(AbstractCircuit):
370
383
  # b
371
384
 
372
385
  # index must be ordered
373
- assert np.all(np.diff(index) > 0)
374
- 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))
375
392
  if isinstance(gate, tn.Node):
376
393
  gate = backend.copy(gate.tensor)
377
- index = np.array(index) - index_left
394
+
378
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)
379
400
  # transform gate from (in1, in2, ..., out1, out2 ...) to
380
401
  # (in1, out1, in2, out2, ...)
381
- order = tuple(np.arange(2 * nindex).reshape((2, nindex)).T.flatten())
382
- shape = (4,) * nindex
383
- gate = backend.reshape(backend.transpose(gate, order), shape)
384
- argsort = np.argsort(index)
402
+ order = tuple(np.arange(2 * nindex).reshape(2, nindex).T.flatten().tolist())
403
+ gate = backend.transpose(gate, order)
385
404
  # reorder the gate according to the site positions
386
- gate = backend.transpose(gate, tuple(argsort))
387
- index = index[argsort] # type: ignore
405
+ gate = backend.reshape(gate, (dim_phys_mpo,) * nindex)
388
406
  # split the gate into tensors assuming they are adjacent
389
- 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
+ )
390
410
  # each tensor is in shape of (i, a, b, j)
391
- tensors = []
392
- previous_i = None
393
- for i, main_tensor in zip(index, main_tensors):
394
- # 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):
395
416
  if previous_i is not None:
396
- for _ in range(previous_i + 1, i):
397
- bond_dim = tensors[-1].shape[-1]
398
- I = (
399
- np.eye(bond_dim * 2)
400
- .reshape((bond_dim, 2, bond_dim, 2))
401
- .transpose((0, 1, 3, 2))
402
- .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])
403
421
  )
404
- tensors.append(backend.convert_to_tensor(I))
405
- nleft, _, nright = main_tensor.shape
406
- 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)))
407
428
  tensors.append(tensor)
408
- previous_i = i
429
+ previous_i = int(i)
430
+
409
431
  return tensors, index_left
410
432
 
411
433
  @classmethod
@@ -448,15 +470,15 @@ class MPSCircuit(AbstractCircuit):
448
470
  """
449
471
  if split is None:
450
472
  split = {}
451
- ni = tensor_left.shape[0]
452
- 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]
453
475
  T = backend.einsum("iaj,jbk->iabk", tensor_left, tensor_right)
454
- T = backend.reshape(T, (ni * 2, nk * 2))
476
+ T = backend.reshape(T, (ni * di, nk * dk))
455
477
  new_tensor_left, new_tensor_right = split_tensor(
456
478
  T, center_left=center_left, split=split
457
479
  )
458
- new_tensor_left = backend.reshape(new_tensor_left, (ni, 2, -1))
459
- 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))
460
482
  return new_tensor_left, new_tensor_right
461
483
 
462
484
  def reduce_dimension(
@@ -550,10 +572,11 @@ class MPSCircuit(AbstractCircuit):
550
572
  for i, idx in zip(i_list, idx_list):
551
573
  O = tensors[i]
552
574
  T = self._mps.tensors[idx]
553
- ni, _, _, nj = O.shape
575
+ ni, d_in, _, nj = O.shape
554
576
  nk, _, nl = T.shape
555
577
  OT = backend.einsum("iabj,kbl->ikajl", O, T)
556
- OT = backend.reshape(OT, (ni * nk, 2, nj * nl))
578
+ OT = backend.reshape(OT, (ni * nk, d_in, nj * nl))
579
+
557
580
  self._mps.tensors[idx] = OT
558
581
 
559
582
  # canonicalize
@@ -660,8 +683,7 @@ class MPSCircuit(AbstractCircuit):
660
683
  :type keep: int, optional
661
684
  """
662
685
  # normalization not guaranteed
663
- assert keep in [0, 1]
664
- gate = backend.zeros((2, 2), dtype=dtypestr)
686
+ gate = backend.zeros((self._d, self._d), dtype=dtypestr)
665
687
  gate = backend.scatter(
666
688
  gate,
667
689
  backend.convert_to_tensor([[keep, keep]]),
@@ -692,7 +714,7 @@ class MPSCircuit(AbstractCircuit):
692
714
  def wavefunction_to_tensors(
693
715
  cls,
694
716
  wavefunction: Tensor,
695
- dim_phys: int = 2,
717
+ dim_phys: Optional[int] = None,
696
718
  norm: bool = True,
697
719
  split: Optional[Dict[str, Any]] = None,
698
720
  ) -> List[Tensor]:
@@ -710,6 +732,7 @@ class MPSCircuit(AbstractCircuit):
710
732
  :return: The tensors
711
733
  :rtype: List[Tensor]
712
734
  """
735
+ dim_phys = dim_phys if dim_phys is not None else 2
713
736
  if split is None:
714
737
  split = {}
715
738
  wavefunction = backend.reshape(wavefunction, (-1, 1))
@@ -768,10 +791,16 @@ class MPSCircuit(AbstractCircuit):
768
791
  for key in vars(self):
769
792
  if key == "_mps":
770
793
  continue
771
- if backend.is_tensor(info[key]):
772
- 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
773
799
  else:
774
- copied_value = copy(info[key])
800
+ try:
801
+ copied_value = copy(val)
802
+ except TypeError:
803
+ copied_value = val
775
804
  setattr(result, key, copied_value)
776
805
  return result
777
806
 
@@ -815,7 +844,8 @@ class MPSCircuit(AbstractCircuit):
815
844
 
816
845
  def amplitude(self, l: str) -> Tensor:
817
846
  assert len(l) == self._nqubits
818
- 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)]
819
849
  return reduce(backend.matmul, tensors)[0, 0]
820
850
 
821
851
  def proj_with_mps(self, other: "MPSCircuit", conj: bool = True) -> Tensor:
@@ -873,6 +903,7 @@ class MPSCircuit(AbstractCircuit):
873
903
 
874
904
  mps = self.__class__(
875
905
  nqubits,
906
+ dim=self._d,
876
907
  tensors=tensors,
877
908
  center_position=center_position,
878
909
  split=self.split.copy(),
@@ -1000,36 +1031,35 @@ class MPSCircuit(AbstractCircuit):
1000
1031
  # set the center to the left side, then gradually move to the right and do measurement at sites
1001
1032
  """
1002
1033
  mps = self.copy()
1003
- up = backend.convert_to_tensor(np.array([1, 0]).astype(dtypestr))
1004
- down = backend.convert_to_tensor(np.array([0, 1]).astype(dtypestr))
1005
1034
 
1006
1035
  p = 1.0
1007
1036
  p = backend.convert_to_tensor(p)
1008
1037
  p = backend.cast(p, dtype=rdtypestr)
1009
- sample = []
1038
+ sample: Tensor = []
1010
1039
  for k, site in enumerate(index):
1011
1040
  mps.position(site)
1012
- # do measurement
1013
1041
  tensor = mps._mps.tensors[site]
1014
1042
  ps = backend.real(
1015
1043
  backend.einsum("iaj,iaj->a", tensor, backend.conj(tensor))
1016
1044
  )
1017
1045
  ps /= backend.sum(ps)
1018
- pu = ps[0]
1019
1046
  if status is None:
1020
- r = backend.implicit_randu()[0]
1047
+ outcome = backend.implicit_randc(
1048
+ self._d, shape=1, p=backend.cast(ps, rdtypestr)
1049
+ )[0]
1021
1050
  else:
1022
- r = status[k]
1023
- r = backend.real(backend.cast(r, dtypestr))
1024
- eps = 0.31415926 * 1e-12
1025
- sign = backend.sign(r - pu + eps) / 2 + 0.5 # in case status is exactly 0.5
1026
- sign = backend.convert_to_tensor(sign)
1027
- sign = backend.cast(sign, dtype=rdtypestr)
1028
- sign_complex = backend.cast(sign, dtypestr)
1029
- sample.append(sign_complex)
1030
- p = p * (pu * (-1) ** sign + sign)
1031
- 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]
1032
1061
  mps._mps.tensors[site] = backend.einsum("iaj,a->ij", tensor, m)[:, None, :]
1062
+ sample.append(outcome)
1033
1063
  sample = backend.stack(sample)
1034
1064
  sample = backend.real(sample)
1035
1065
  if with_prob: