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/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
|
|
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 =
|
|
90
|
+
n = _infer_num_sites(N, dim=self._d)
|
|
83
91
|
assert n == nqubits or n == 2 * nqubits
|
|
84
|
-
inputs = backend.reshape(inputs, [
|
|
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:
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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, [
|
|
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,
|
|
603
|
+
new_kraus,
|
|
604
|
+
*index,
|
|
605
|
+
prob=prob,
|
|
606
|
+
status=status,
|
|
607
|
+
name=name,
|
|
609
608
|
)
|
|
610
|
-
if with_prob
|
|
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,
|
|
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:
|
|
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([
|
|
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([
|
|
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
|
-
|
|
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
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
|
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.
|
|
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.
|
|
1015
|
-
bra = backend.
|
|
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.
|
|
1037
|
+
op = backend.reshaped(op, dim)
|
|
1028
1038
|
op = gates.Gate(op)
|
|
1029
1039
|
if isinstance(index, int):
|
|
1030
1040
|
index = [index]
|
tensorcircuit/cloud/local.py
CHANGED
|
@@ -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"]:
|
tensorcircuit/cloud/tencent.py
CHANGED
|
@@ -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
|
|
tensorcircuit/densitymatrix.py
CHANGED
|
@@ -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 =
|
|
79
|
+
n = _infer_num_sites(N, self._d)
|
|
77
80
|
assert n == nqubits
|
|
78
|
-
inputs = backend.reshape(inputs, [
|
|
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(
|
|
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
|
|
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.
|
|
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.
|
|
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([
|
|
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, [
|
|
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)
|
tensorcircuit/experimental.py
CHANGED
|
@@ -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,
|
|
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
|