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/circuit.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """
2
- Quantum circuit: the state simulator
2
+ Quantum circuit: the state simulator.
3
+ Supports qubit (dim=2) and qudit (3 <= dim <= 36) systems.
4
+ For string-encoded samples/counts, digits use 0-9A-Z where A=10, ..., Z=35.
3
5
  """
4
6
 
5
7
  # pylint: disable=invalid-name
@@ -13,8 +15,8 @@ import tensornetwork as tn
13
15
 
14
16
  from . import gates
15
17
  from . import channels
16
- from .cons import backend, contractor, dtypestr, npdtype
17
- from .quantum import QuOperator, identity
18
+ from .cons import backend, contractor, dtypestr, npdtype, _ALPHABET
19
+ from .quantum import QuOperator, identity, _infer_num_sites, onehot_d_tensor
18
20
  from .simplify import _full_light_cone_cancel
19
21
  from .basecircuit import BaseCircuit
20
22
 
@@ -23,7 +25,7 @@ Tensor = Any
23
25
 
24
26
 
25
27
  class Circuit(BaseCircuit):
26
- """
28
+ r"""
27
29
  ``Circuit`` class.
28
30
  Simple usage demo below.
29
31
 
@@ -45,14 +47,18 @@ class Circuit(BaseCircuit):
45
47
  inputs: Optional[Tensor] = None,
46
48
  mps_inputs: Optional[QuOperator] = None,
47
49
  split: Optional[Dict[str, Any]] = None,
50
+ dim: Optional[int] = None,
48
51
  ) -> None:
49
- """
52
+ r"""
50
53
  Circuit object based on state simulator.
54
+ Do not use this class with d!=2 directly, use tc.QuditCircuit instead for qudit systems.
51
55
 
52
56
  :param nqubits: The number of qubits in the circuit.
53
57
  :type nqubits: int
58
+ :param dim: The local Hilbert space dimension per site. Qudit is supported for 2 <= d <= 36.
59
+ :type dim: If None, the dimension of the circuit will be `2`, which is a qubit system.
54
60
  :param inputs: If not None, the initial state of the circuit is taken as ``inputs``
55
- instead of :math:`\\vert 0\\rangle^n` qubits, defaults to None.
61
+ instead of :math:`\vert 0 \rangle^n` qubits, defaults to None.
56
62
  :type inputs: Optional[Tensor], optional
57
63
  :param mps_inputs: QuVector for a MPS like initial wavefunction.
58
64
  :type mps_inputs: Optional[QuOperator]
@@ -60,6 +66,7 @@ class Circuit(BaseCircuit):
60
66
  ``max_singular_values`` and ``max_truncation_err``.
61
67
  :type split: Optional[Dict[str, Any]]
62
68
  """
69
+ self._d = 2 if dim is None else dim
63
70
  self.inputs = inputs
64
71
  self.mps_inputs = mps_inputs
65
72
  self.split = split
@@ -70,18 +77,19 @@ class Circuit(BaseCircuit):
70
77
  "inputs": inputs,
71
78
  "mps_inputs": mps_inputs,
72
79
  "split": split,
80
+ "dim": dim,
73
81
  }
74
82
  if (inputs is None) and (mps_inputs is None):
75
- nodes = self.all_zero_nodes(nqubits)
83
+ nodes = self.all_zero_nodes(nqubits, dim=self._d)
76
84
  self._front = [n.get_edge(0) for n in nodes]
77
85
  elif inputs is not None: # provide input function
78
86
  inputs = backend.convert_to_tensor(inputs)
79
87
  inputs = backend.cast(inputs, dtype=dtypestr)
80
88
  inputs = backend.reshape(inputs, [-1])
81
89
  N = inputs.shape[0]
82
- n = int(np.log(N) / np.log(2))
90
+ n = _infer_num_sites(N, dim=self._d)
83
91
  assert n == nqubits or n == 2 * nqubits
84
- inputs = backend.reshape(inputs, [2 for _ in range(n)])
92
+ inputs = backend.reshape(inputs, [self._d for _ in range(n)])
85
93
  inputs = Gate(inputs)
86
94
  nodes = [inputs]
87
95
  self._front = [inputs.get_edge(i) for i in range(n)]
@@ -178,27 +186,14 @@ class Circuit(BaseCircuit):
178
186
 
179
187
  :param index: The index of qubit that the Z direction postselection applied on.
180
188
  :type index: int
181
- :param keep: 0 for spin up, 1 for spin down, defaults to be 0.
189
+ :param keep: the post-selected digit in {0, ..., d-1}, defaults to be 0.
182
190
  :type keep: int, optional
183
191
  """
184
192
  # normalization not guaranteed
185
- # assert keep in [0, 1]
186
- if keep < 0.5:
187
- gate = np.array(
188
- [
189
- [1.0],
190
- [0.0],
191
- ],
192
- dtype=npdtype,
193
- )
194
- else:
195
- gate = np.array(
196
- [
197
- [0.0],
198
- [1.0],
199
- ],
200
- dtype=npdtype,
201
- )
193
+ gate = np.array(
194
+ [[0.0] if _idx != keep else [1.0] for _idx in range(self._d)],
195
+ dtype=npdtype,
196
+ )
202
197
 
203
198
  mg1 = tn.Node(gate)
204
199
  mg2 = tn.Node(gate)
@@ -479,8 +474,8 @@ class Circuit(BaseCircuit):
479
474
  if get_gate_from_index is None:
480
475
  raise ValueError("no `get_gate_from_index` implementation is provided")
481
476
  g = get_gate_from_index(r, kraus)
482
- g = backend.reshape(g, [2 for _ in range(sites * 2)])
483
- self.any(*index, unitary=g, name=name) # type: ignore
477
+ g = backend.reshape(g, [self._d for _ in range(sites * 2)])
478
+ self.any(*index, unitary=g, name=name, dim=self._d) # type: ignore
484
479
  return r
485
480
 
486
481
  def _general_kraus_tf(
@@ -605,9 +600,13 @@ class Circuit(BaseCircuit):
605
600
  for w, k in zip(prob, kraus_tensor)
606
601
  ]
607
602
  pick = self.unitary_kraus(
608
- new_kraus, *index, prob=prob, status=status, name=name
603
+ new_kraus,
604
+ *index,
605
+ prob=prob,
606
+ status=status,
607
+ name=name,
609
608
  )
610
- if with_prob is False:
609
+ if not with_prob:
611
610
  return pick
612
611
  else:
613
612
  return pick, prob
@@ -638,7 +637,11 @@ class Circuit(BaseCircuit):
638
637
  :type status: Optional[float], optional
639
638
  """
640
639
  return self._general_kraus_2(
641
- kraus, *index, status=status, with_prob=with_prob, name=name
640
+ kraus,
641
+ *index,
642
+ status=status,
643
+ with_prob=with_prob,
644
+ name=name,
642
645
  )
643
646
 
644
647
  apply_general_kraus = general_kraus
@@ -680,7 +683,7 @@ class Circuit(BaseCircuit):
680
683
  Apply %s quantum channel on the circuit.
681
684
  See :py:meth:`tensorcircuit.channels.%schannel`
682
685
 
683
- :param index: Qubit number that the gate applies on.
686
+ :param index: Site index that the gate applies on.
684
687
  :type index: int.
685
688
  :param status: uniform external random number between 0 and 1
686
689
  :type status: Tensor
@@ -737,8 +740,8 @@ class Circuit(BaseCircuit):
737
740
  :return: ``QuOperator`` object for the circuit unitary (open indices for the input state)
738
741
  :rtype: QuOperator
739
742
  """
740
- mps = identity([2 for _ in range(self._nqubits)])
741
- c = Circuit(self._nqubits)
743
+ mps = identity([self._d for _ in range(self._nqubits)])
744
+ c = Circuit(self._nqubits, dim=self._d)
742
745
  ns, es = self._copy()
743
746
  c._nodes = ns
744
747
  c._front = es
@@ -758,8 +761,8 @@ class Circuit(BaseCircuit):
758
761
  :return: The circuit unitary matrix
759
762
  :rtype: Tensor
760
763
  """
761
- mps = identity([2 for _ in range(self._nqubits)])
762
- c = Circuit(self._nqubits)
764
+ mps = identity([self._d for _ in range(self._nqubits)])
765
+ c = Circuit(self._nqubits, dim=self._d)
763
766
  ns, es = self._copy()
764
767
  c._nodes = ns
765
768
  c._front = es
@@ -772,6 +775,9 @@ class Circuit(BaseCircuit):
772
775
  """
773
776
  Take measurement on the given quantum lines by ``index``.
774
777
 
778
+ Return format:
779
+ - For d <= 36, the sample is a base-d string using 0-9A-Z (A=10,...).
780
+
775
781
  :Example:
776
782
 
777
783
  >>> c = tc.Circuit(3)
@@ -800,10 +806,7 @@ class Circuit(BaseCircuit):
800
806
  if i != j:
801
807
  e ^ edge2[i]
802
808
  for i in range(len(sample)):
803
- if sample[i] == "0":
804
- m = np.array([1, 0], dtype=npdtype)
805
- else:
806
- m = np.array([0, 1], dtype=npdtype)
809
+ m = onehot_d_tensor(sample[i], d=self._d)
807
810
  nodes1.append(tn.Node(m))
808
811
  nodes1[-1].get_edge(0) ^ edge1[index[i]]
809
812
  nodes2.append(tn.Node(m))
@@ -814,15 +817,13 @@ class Circuit(BaseCircuit):
814
817
  / p
815
818
  * contractor(nodes1, output_edge_order=[edge1[j], edge2[j]]).tensor
816
819
  )
817
- pu = rho[0, 0]
818
- r = backend.random_uniform([])
819
- r = backend.real(backend.cast(r, dtypestr))
820
- if r < backend.real(pu):
821
- sample += "0"
822
- p = p * pu
823
- else:
824
- sample += "1"
825
- p = p * (1 - pu)
820
+ probs = backend.real(backend.diagonal(rho))
821
+ probs /= backend.sum(probs)
822
+ outcome = backend.implicit_randc(self._d, shape=1, p=probs)
823
+
824
+ sample += _ALPHABET[outcome]
825
+ p *= float(probs[outcome])
826
+
826
827
  if with_prob:
827
828
  return sample, p
828
829
  else:
@@ -842,6 +843,10 @@ class Circuit(BaseCircuit):
842
843
  ) -> Tensor:
843
844
  """
844
845
  Compute the expectation of corresponding operators.
846
+ For qudit (d > 2),
847
+ ensure that operator tensor shapes are consistent with d (each site contributes two axes of size d).
848
+
849
+ Noise shorthand (via noise_conf) is qubit-only; for d>2, use explicit operators.
845
850
 
846
851
  :Example:
847
852
 
@@ -877,14 +882,12 @@ class Circuit(BaseCircuit):
877
882
  :param nmc: repetition time for Monte Carlo sampling for noisfy calculation, defaults to 1000
878
883
  :type nmc: int, optional
879
884
  :param status: external randomness given by tensor uniformly from [0, 1], defaults to None,
880
- used for noisfy circuit sampling
885
+ used for noisy circuit sampling
881
886
  :type status: Optional[Tensor], optional
882
887
  :raises ValueError: "Cannot measure two operators in one index"
883
888
  :return: Tensor with one element
884
889
  :rtype: Tensor
885
890
  """
886
- from .noisemodel import expectation_noisfy
887
-
888
891
  if noise_conf is None:
889
892
  # if not reuse:
890
893
  # nodes1, edge1 = self._copy()
@@ -899,6 +902,8 @@ class Circuit(BaseCircuit):
899
902
  nodes1 = _full_light_cone_cancel(nodes1)
900
903
  return contractor(nodes1).tensor
901
904
  else:
905
+ from .noisemodel import expectation_noisfy
906
+
902
907
  return expectation_noisfy(
903
908
  self,
904
909
  *ops,
@@ -919,9 +924,11 @@ def expectation(
919
924
  bra: Optional[Tensor] = None,
920
925
  conj: bool = True,
921
926
  normalization: bool = False,
927
+ dim: Optional[int] = None,
922
928
  ) -> Tensor:
923
929
  """
924
930
  Compute :math:`\\langle bra\\vert ops \\vert ket\\rangle`.
931
+ For qudit systems (d>2), ops must be reshaped with per-site axes of length d.
925
932
 
926
933
  Example 1 (:math:`bra` is same as :math:`ket`)
927
934
 
@@ -966,6 +973,8 @@ def expectation(
966
973
  :type ket: Tensor
967
974
  :param bra: :math:`bra`, defaults to None, which is the same as ``ket``.
968
975
  :type bra: Optional[Tensor], optional
976
+ :param dim: dimension of the circuit (defaults to 2)
977
+ :type dim: int, optional
969
978
  :param conj: :math:`bra` changes to the adjoint matrix of :math:`bra`, defaults to True.
970
979
  :type conj: bool, optional
971
980
  :param normalization: Normalize the :math:`ket` and :math:`bra`, defaults to False.
@@ -974,6 +983,7 @@ def expectation(
974
983
  :return: The result of :math:`\\langle bra\\vert ops \\vert ket\\rangle`.
975
984
  :rtype: Tensor
976
985
  """
986
+ dim = 2 if dim is None else dim
977
987
  if bra is None:
978
988
  bra = ket
979
989
  if isinstance(ket, QuOperator):
@@ -987,7 +997,7 @@ def expectation(
987
997
  for op, index in ops:
988
998
  if not isinstance(op, tn.Node):
989
999
  # op is only a matrix
990
- op = backend.reshape2(op)
1000
+ op = backend.reshaped(op, dim)
991
1001
  op = gates.Gate(op)
992
1002
  if isinstance(index, int):
993
1003
  index = [index]
@@ -1011,8 +1021,8 @@ def expectation(
1011
1021
  if conj is True:
1012
1022
  bra = backend.conj(bra)
1013
1023
  ket = backend.reshape(ket, [-1])
1014
- ket = backend.reshape2(ket)
1015
- bra = backend.reshape2(bra)
1024
+ ket = backend.reshaped(ket, dim)
1025
+ bra = backend.reshaped(bra, dim)
1016
1026
  n = len(backend.shape_tuple(ket))
1017
1027
  ket = Gate(ket)
1018
1028
  bra = Gate(bra)
@@ -1024,7 +1034,7 @@ def expectation(
1024
1034
  for op, index in ops:
1025
1035
  if not isinstance(op, tn.Node):
1026
1036
  # op is only a matrix
1027
- op = backend.reshape2(op)
1037
+ op = backend.reshaped(op, dim)
1028
1038
  op = gates.Gate(op)
1029
1039
  if isinstance(index, int):
1030
1040
  index = [index]
@@ -36,7 +36,7 @@ def submit_task(
36
36
  shots: Union[int, Sequence[int]] = 1024,
37
37
  version: str = "1",
38
38
  circuit: Optional[Union[AbstractCircuit, Sequence[AbstractCircuit]]] = None,
39
- **kws: Any
39
+ **kws: Any,
40
40
  ) -> List[Task]:
41
41
  def _circuit2result(c: AbstractCircuit) -> Dict[str, Any]:
42
42
  if device.name in ["testing", "default"]:
@@ -30,7 +30,7 @@ def submit_task(
30
30
  circuit: Optional[Union[AbstractCircuit, Sequence[AbstractCircuit]]] = None,
31
31
  source: Optional[Union[str, Sequence[str]]] = None,
32
32
  compile: bool = True,
33
- **kws: Any
33
+ **kws: Any,
34
34
  ) -> Task:
35
35
  if source is None:
36
36
 
@@ -133,7 +133,7 @@ def submit_task(
133
133
  enable_qos_gate_decomposition: bool = True,
134
134
  enable_qos_initial_mapping: bool = False,
135
135
  qos_dry_run: bool = False,
136
- **kws: Any
136
+ **kws: Any,
137
137
  ) -> List[Task]:
138
138
  """
139
139
  Submit task via tencent provider, we suggest to enable one of the compiling functionality:
@@ -109,7 +109,7 @@ def prune(
109
109
  circuit: Union[AbstractCircuit, List[Dict[str, Any]]],
110
110
  rtol: float = 1e-3,
111
111
  atol: float = 1e-3,
112
- **kws: Any
112
+ **kws: Any,
113
113
  ) -> Any:
114
114
  if isinstance(circuit, list):
115
115
  qir = circuit
@@ -251,7 +251,7 @@ def _merge(
251
251
  def merge(
252
252
  circuit: Union[AbstractCircuit, List[Dict[str, Any]]],
253
253
  rules: Optional[Dict[Tuple[str, ...], str]] = None,
254
- **kws: Any
254
+ **kws: Any,
255
255
  ) -> Any:
256
256
  merge_rules = copy(default_merge_rules)
257
257
  if rules is not None:
tensorcircuit/cons.py CHANGED
@@ -63,6 +63,7 @@ rdtypestr = "float32"
63
63
  npdtype = np.complex64
64
64
  backend: NumpyBackend = get_backend("numpy")
65
65
  contractor = tn.contractors.auto
66
+ _ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
66
67
  # these above lines are just for mypy, it is not very good at evaluating runtime object
67
68
 
68
69
 
@@ -17,7 +17,7 @@ from .channels import kraus_to_super_gate
17
17
  from .circuit import Circuit
18
18
  from .cons import backend, contractor, dtypestr
19
19
  from .basecircuit import BaseCircuit
20
- from .quantum import QuOperator
20
+ from .quantum import QuOperator, _infer_num_sites
21
21
 
22
22
  Gate = gates.Gate
23
23
  Tensor = Any
@@ -35,9 +35,11 @@ class DMCircuit(BaseCircuit):
35
35
  dminputs: Optional[Tensor] = None,
36
36
  mpo_dminputs: Optional[QuOperator] = None,
37
37
  split: Optional[Dict[str, Any]] = None,
38
+ dim: Optional[int] = None,
38
39
  ) -> None:
39
40
  """
40
41
  The density matrix simulator based on tensornetwork engine.
42
+ Do not use this class with d!=2 directly
41
43
 
42
44
  :param nqubits: Number of qubits
43
45
  :type nqubits: int
@@ -55,6 +57,7 @@ class DMCircuit(BaseCircuit):
55
57
  ``max_singular_values`` and ``max_truncation_err``.
56
58
  :type split: Optional[Dict[str, Any]]
57
59
  """
60
+ self._d = 2 if dim is None else dim
58
61
  if not empty:
59
62
  if (
60
63
  (inputs is None)
@@ -73,9 +76,9 @@ class DMCircuit(BaseCircuit):
73
76
  inputs = backend.cast(inputs, dtype=dtypestr)
74
77
  inputs = backend.reshape(inputs, [-1])
75
78
  N = inputs.shape[0]
76
- n = int(np.log(N) / np.log(2))
79
+ n = _infer_num_sites(N, self._d)
77
80
  assert n == nqubits
78
- inputs = backend.reshape(inputs, [2 for _ in range(n)])
81
+ inputs = backend.reshape(inputs, [self._d for _ in range(n)])
79
82
  inputs_gate = Gate(inputs)
80
83
  self._nodes = [inputs_gate]
81
84
  self.coloring_nodes(self._nodes)
@@ -94,7 +97,9 @@ class DMCircuit(BaseCircuit):
94
97
  elif dminputs is not None:
95
98
  dminputs = backend.convert_to_tensor(dminputs)
96
99
  dminputs = backend.cast(dminputs, dtype=dtypestr)
97
- dminputs = backend.reshape(dminputs, [2 for _ in range(2 * nqubits)])
100
+ dminputs = backend.reshape(
101
+ dminputs, [self._d for _ in range(2 * nqubits)]
102
+ )
98
103
  dminputs_gate = Gate(dminputs)
99
104
  nodes = [dminputs_gate]
100
105
  self._front = [dminputs_gate.get_edge(i) for i in range(2 * nqubits)]
@@ -174,9 +179,9 @@ class DMCircuit(BaseCircuit):
174
179
 
175
180
  @staticmethod
176
181
  def check_kraus(kraus: Sequence[Gate]) -> bool:
177
- """
182
+ r"""
178
183
  Check if Kraus operators satisfy the completeness relation:
179
- sum_i K_i^† K_i = I
184
+ :math:`\sum_i K_i^\dagger K_i = I`
180
185
 
181
186
  :param kraus: Sequence of Kraus operators
182
187
  :type kraus: Sequence[Gate]
@@ -217,7 +222,7 @@ class DMCircuit(BaseCircuit):
217
222
  dd = dmc.densitymatrix()
218
223
  circuits.append(dd)
219
224
  tensor = reduce(add, circuits)
220
- tensor = backend.reshape(tensor, [2 for _ in range(2 * self._nqubits)])
225
+ tensor = backend.reshaped(tensor, d=self._d)
221
226
  self._nodes = [Gate(tensor)]
222
227
  dangling = [e for e in self._nodes[0]]
223
228
  self._front = dangling
@@ -255,7 +260,7 @@ class DMCircuit(BaseCircuit):
255
260
  t = contractor(nodes, output_edge_order=d_edges)
256
261
  else:
257
262
  t = nodes[0]
258
- dm = backend.reshape(t.tensor, shape=[2**self._nqubits, 2**self._nqubits])
263
+ dm = backend.reshapem(t.tensor)
259
264
  if check:
260
265
  self.check_density_matrix(dm)
261
266
  return dm
@@ -274,7 +279,7 @@ class DMCircuit(BaseCircuit):
274
279
  dm = self.densitymatrix()
275
280
  e, v = backend.eigh(dm)
276
281
  np.testing.assert_allclose(
277
- e[:-1], backend.zeros([2**self._nqubits - 1]), atol=1e-5
282
+ e[:-1], backend.zeros([self._d**self._nqubits - 1]), atol=1e-5
278
283
  )
279
284
  return v[:, -1]
280
285
 
@@ -297,7 +302,7 @@ class DMCircuit(BaseCircuit):
297
302
  reuse: bool = True,
298
303
  noise_conf: Optional[Any] = None,
299
304
  status: Optional[Tensor] = None,
300
- **kws: Any
305
+ **kws: Any,
301
306
  ) -> tn.Node.tensor:
302
307
  """
303
308
  Compute the expectation of corresponding operators.
@@ -375,7 +380,7 @@ class DMCircuit2(DMCircuit):
375
380
  # index = [index[0] for _ in range(len(kraus))]
376
381
  super_op = kraus_to_super_gate(kraus)
377
382
  nlegs = 4 * len(index)
378
- super_op = backend.reshape(super_op, [2 for _ in range(nlegs)])
383
+ super_op = backend.reshape(super_op, [self._d for _ in range(nlegs)])
379
384
  super_op = Gate(super_op)
380
385
  o2i = int(nlegs / 2)
381
386
  r2l = int(nlegs / 4)
@@ -2,14 +2,20 @@
2
2
  Experimental features
3
3
  """
4
4
 
5
+ # pylint: disable=unused-import
6
+
5
7
  from functools import partial
6
8
  import logging
7
9
  from typing import Any, Callable, Dict, Optional, Tuple, List, Sequence, Union
8
10
 
9
11
  import numpy as np
10
12
 
11
- from .cons import backend, dtypestr, contractor, rdtypestr, get_tn_info
13
+ from .cons import backend, dtypestr, rdtypestr, get_tn_info
12
14
  from .gates import Gate
15
+ from .timeevol import hamiltonian_evol, evol_global, evol_local
16
+
17
+
18
+ # for backward compatibility
13
19
 
14
20
  Tensor = Any
15
21
  Circuit = Any
@@ -435,157 +441,6 @@ def finite_difference_differentiator(
435
441
  return tf_function # type: ignore
436
442
 
437
443
 
438
- def hamiltonian_evol(
439
- tlist: Tensor,
440
- h: Tensor,
441
- psi0: Tensor,
442
- callback: Optional[Callable[..., Any]] = None,
443
- ) -> Tensor:
444
- """
445
- Fast implementation of time independent Hamiltonian evolution using eigendecomposition.
446
- By default, performs imaginary time evolution.
447
-
448
- :param tlist: Time points for evolution
449
- :type tlist: Tensor
450
- :param h: Time-independent Hamiltonian matrix
451
- :type h: Tensor
452
- :param psi0: Initial state vector
453
- :type psi0: Tensor
454
- :param callback: Optional function to process state at each time point
455
- :type callback: Optional[Callable[..., Any]], optional
456
- :return: Evolution results at each time point. If callback is None, returns state vectors;
457
- otherwise returns callback results
458
- :rtype: Tensor
459
-
460
- :Example:
461
-
462
- >>> import tensorcircuit as tc
463
- >>> import numpy as np
464
- >>> # Define a simple 2-qubit Hamiltonian
465
- >>> h = tc.array_to_tensor([
466
- ... [1.0, 0.0, 0.0, 0.0],
467
- ... [0.0, -1.0, 2.0, 0.0],
468
- ... [0.0, 2.0, -1.0, 0.0],
469
- ... [0.0, 0.0, 0.0, 1.0]
470
- ... ])
471
- >>> # Initial state |00⟩
472
- >>> psi0 = tc.array_to_tensor([1.0, 0.0, 0.0, 0.0])
473
- >>> # Evolution times
474
- >>> times = tc.array_to_tensor([0.0, 0.5, 1.0])
475
- >>> # Evolve and get states
476
- >>> states = tc.experimental.hamiltonian_evol(times, h, psi0)
477
- >>> print(states.shape) # (3, 4)
478
-
479
-
480
- Note:
481
- 1. The Hamiltonian must be time-independent
482
- 2. For time-dependent Hamiltonians, use ``evol_local`` or ``evol_global`` instead
483
- 3. The evolution is performed in imaginary time by default (factor -t in exponential)
484
- 4. The state is automatically normalized at each time point
485
- """
486
- es, u = backend.eigh(h)
487
- utpsi0 = backend.reshape(
488
- backend.transpose(u) @ backend.reshape(psi0, [-1, 1]), [-1]
489
- )
490
-
491
- @backend.jit
492
- def _evol(t: Tensor) -> Tensor:
493
- ebetah_utpsi0 = backend.exp(-t * es) * utpsi0
494
- psi_exact = backend.conj(u) @ backend.reshape(ebetah_utpsi0, [-1, 1])
495
- psi_exact = backend.reshape(psi_exact, [-1])
496
- psi_exact = psi_exact / backend.norm(psi_exact)
497
- if callback is None:
498
- return psi_exact
499
- return callback(psi_exact)
500
-
501
- return backend.stack([_evol(t) for t in tlist])
502
-
503
-
504
- def evol_local(
505
- c: Circuit,
506
- index: Sequence[int],
507
- h_fun: Callable[..., Tensor],
508
- t: float,
509
- *args: Any,
510
- **solver_kws: Any,
511
- ) -> Circuit:
512
- """
513
- ode evolution of time dependent Hamiltonian on circuit of given indices
514
- [only jax backend support for now]
515
-
516
- :param c: _description_
517
- :type c: Circuit
518
- :param index: qubit sites to evolve
519
- :type index: Sequence[int]
520
- :param h_fun: h_fun should return a dense Hamiltonian matrix
521
- with input arguments time and *args
522
- :type h_fun: Callable[..., Tensor]
523
- :param t: evolution time
524
- :type t: float
525
- :return: _description_
526
- :rtype: Circuit
527
- """
528
- from jax.experimental.ode import odeint
529
-
530
- s = c.state()
531
- n = c._nqubits
532
- l = len(index)
533
-
534
- def f(y: Tensor, t: Tensor, *args: Any) -> Tensor:
535
- y = backend.reshape2(y)
536
- y = Gate(y)
537
- h = -1.0j * h_fun(t, *args)
538
- h = backend.reshape2(h)
539
- h = Gate(h)
540
- edges = []
541
- for i in range(n):
542
- if i not in index:
543
- edges.append(y[i])
544
- else:
545
- j = index.index(i)
546
- edges.append(h[j])
547
- h[j + l] ^ y[i]
548
- y = contractor([y, h], output_edge_order=edges)
549
- return backend.reshape(y.tensor, [-1])
550
-
551
- ts = backend.stack([0.0, t])
552
- ts = backend.cast(ts, dtype=rdtypestr)
553
- s1 = odeint(f, s, ts, *args, **solver_kws)
554
- return type(c)(n, inputs=s1[-1])
555
-
556
-
557
- def evol_global(
558
- c: Circuit, h_fun: Callable[..., Tensor], t: float, *args: Any, **solver_kws: Any
559
- ) -> Circuit:
560
- """
561
- ode evolution of time dependent Hamiltonian on circuit of all qubits
562
- [only jax backend support for now]
563
-
564
- :param c: _description_
565
- :type c: Circuit
566
- :param h_fun: h_fun should return a **SPARSE** Hamiltonian matrix
567
- with input arguments time and *args
568
- :type h_fun: Callable[..., Tensor]
569
- :param t: _description_
570
- :type t: float
571
- :return: _description_
572
- :rtype: Circuit
573
- """
574
- from jax.experimental.ode import odeint
575
-
576
- s = c.state()
577
- n = c._nqubits
578
-
579
- def f(y: Tensor, t: Tensor, *args: Any) -> Tensor:
580
- h = -1.0j * h_fun(t, *args)
581
- return backend.sparse_dense_matmul(h, y)
582
-
583
- ts = backend.stack([0.0, t])
584
- ts = backend.cast(ts, dtype=rdtypestr)
585
- s1 = odeint(f, s, ts, *args, **solver_kws)
586
- return type(c)(n, inputs=s1[-1])
587
-
588
-
589
444
  def jax_jitted_function_save(filename: str, f: Callable[..., Any], *args: Any) -> None:
590
445
  """
591
446
  save a jitted jax function as a file