Skip to content

Commit 7dd9a17

Browse files
merge pr28
2 parents 0401d4b + be5bf21 commit 7dd9a17

File tree

7 files changed

+1151
-48
lines changed

7 files changed

+1151
-48
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
- Add `argsort` method for backends
2424

25+
- Add transformation method between tensornetwork, quimb, tenpy and QuOperator in tc-ng including `qop2tenpy`, `qop2quimb`, `qop2tn`, `tenpy2qop`, support both MPS and MPO formats.
26+
2527
### Fixed
2628

2729
- Fixed `one_hot` in numpy backend.
@@ -30,6 +32,8 @@
3032

3133
- Fix potential np.matrix return from `PaulistringSum2Dense`.
3234

35+
- `MPSCircuit` now will first try to transform `QuVector` input to tensors directly instead of evaluating it to dense wavefunction first.
36+
3337
### Changed
3438

3539
- The order of arguments of `tc.timeevol.ed_evol` are changed for consistent interface with other evolution methods.

examples/tenpy_sz_convention.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
Demonstrates the different internal basis conventions between
3+
TeNPy's TFIChain and XXZChain models and showcases a robust
4+
method for handling these inconsistencies when converting to TensorCircuit.
5+
6+
1. TFIChain: Shows a direct conversion works perfectly.
7+
2. XXZChain:
8+
a. Runs DMRG to obtain a non-trivial ground state.
9+
b. Shows that direct conversion leads to incorrect expectation values for correlation functions.
10+
c. Demonstrates that applying a layer of X-gates in TensorCircuit
11+
3. Tensor Dissection: Provides definitive proof of the differing internal basis conventions between the two models.
12+
"""
13+
14+
import numpy as np
15+
from tenpy.networks.mps import MPS
16+
from tenpy.models.tf_ising import TFIChain
17+
from tenpy.models.xxz_chain import XXZChain
18+
from tenpy.algorithms import dmrg
19+
20+
import tensorcircuit as tc
21+
22+
print("Scenario 1: Antiferromagnetic State (TFIChain)")
23+
L = 10
24+
afm_model_params = {"L": L, "bc_MPS": "finite", "J": 1.0, "g": 0.0, "conserve": None}
25+
afm_M = TFIChain(afm_model_params)
26+
afm_state = ["up", "down"] * (L // 2)
27+
print(f"Testing with a simple product state: {afm_state}")
28+
29+
psi_afm = MPS.from_product_state(afm_M.lat.mps_sites(), afm_state, bc=afm_M.lat.bc_MPS)
30+
tc_afm_state = tc.quantum.tenpy2qop(psi_afm)
31+
circuit_afm = tc.MPSCircuit(L, wavefunction=tc_afm_state)
32+
33+
mag_z_afm_tenpy = psi_afm.expectation_value("Sz")
34+
mag_z_afm_tc = np.array(
35+
[
36+
tc.backend.numpy(tc.backend.real(circuit_afm.expectation((tc.gates.z(), [i]))))
37+
/ 2.0
38+
for i in range(L)
39+
]
40+
)
41+
print("\nAntiferromagnetic state site-by-site magnetization comparison:")
42+
print("TeNPy:", np.round(mag_z_afm_tenpy, 8))
43+
print("TC: ", np.round(mag_z_afm_tc, 8))
44+
np.testing.assert_allclose(mag_z_afm_tenpy, mag_z_afm_tc, atol=1e-5)
45+
print(
46+
"\n[SUCCESS] TFI-based Antiferromagnetic state matches perfectly with the pure converter."
47+
)
48+
49+
50+
print("Scenario 2: XXZChain Model")
51+
xxz_model_params = {"L": L, "bc_MPS": "finite", "Jxx": 1.0, "Jz": 0.5, "hz": 0.1}
52+
xxz_M = XXZChain(xxz_model_params)
53+
example_state = ["up", "down", "up", "up", "down", "down", "up", "down", "down", "up"]
54+
print(f"Testing with a random product state: {example_state}")
55+
psi_rand_xxz = MPS.from_product_state(
56+
xxz_M.lat.mps_sites(), example_state, bc=xxz_M.lat.bc_MPS
57+
)
58+
tc_rand_xxz_state = tc.quantum.tenpy2qop(psi_rand_xxz)
59+
circuit_rand_xxz = tc.MPSCircuit(L, wavefunction=tc_rand_xxz_state)
60+
mag_z_rand_xxz_tenpy = psi_rand_xxz.expectation_value("Sz")
61+
mag_z_rand_xxz_tc = np.array(
62+
[
63+
tc.backend.numpy(
64+
tc.backend.real(circuit_rand_xxz.expectation((tc.gates.z(), [i])))
65+
)
66+
/ 2.0
67+
for i in range(L)
68+
]
69+
)
70+
print("\nXXZ-based random state site-by-site magnetization comparison:")
71+
print("TeNPy:", np.round(mag_z_rand_xxz_tenpy, 8))
72+
print("TC: ", np.round(mag_z_rand_xxz_tc, 8))
73+
try:
74+
np.testing.assert_allclose(mag_z_rand_xxz_tenpy, mag_z_rand_xxz_tc, atol=1e-5)
75+
except AssertionError as e:
76+
print("\n[SUCCESS] As expected, the direct comparison fails for XXZChain.")
77+
print(
78+
"This is because the pure converter does not handle its inverted basis convention."
79+
)
80+
print("\nVerifying that the values match after correcting the sign:")
81+
np.testing.assert_allclose(mag_z_rand_xxz_tenpy, -mag_z_rand_xxz_tc, atol=1e-5)
82+
print(
83+
"[SUCCESS] Test passes after applying the sign correction for the XXZChain model."
84+
)
85+
86+
87+
print("Scenario 3: Tensor Dissection for Both Models")
88+
simple_L = 2
89+
simple_labels = ["up", "down"]
90+
print("\nDissecting TFIChain-based Tensors")
91+
sites_tfi = afm_M.lat.mps_sites()[:simple_L]
92+
psi_simple_tfi = MPS.from_product_state(sites_tfi, simple_labels, bc="finite")
93+
for i, label in enumerate(simple_labels):
94+
B_tensor = psi_simple_tfi.get_B(i).to_ndarray()
95+
print(
96+
f"For '{label}', TFIChain internal tensor has non-zero at physical index {np.where(B_tensor[0,:,0] != 0)[0][0]}"
97+
)
98+
print("\nDissecting XXZChain-based Tensors")
99+
sites_xxz = xxz_M.lat.mps_sites()[:simple_L]
100+
psi_simple_xxz = MPS.from_product_state(sites_xxz, simple_labels, bc="finite")
101+
for i, label in enumerate(simple_labels):
102+
B_tensor = psi_simple_xxz.get_B(i).to_ndarray()
103+
print(
104+
f"For '{label}', XXZChain internal tensor has non-zero at physical index {np.where(B_tensor[0,:,0] != 0)[0][0]}"
105+
)
106+
print("\n Conclusion")
107+
print("The dissection above shows the root cause of the different behaviors:")
108+
print(
109+
" - TFIChain's 'up' maps to index 0, 'down' to index 1. This matches TC's standard."
110+
)
111+
print(
112+
" - XXZChain's 'up' maps to index 1, 'down' to index 0. This is INVERTED from TC's standard."
113+
)
114+
print("\nTherefore, a single, universal converter is not feasible without context.")
115+
print(
116+
"The robust solution is to use a pure converter and apply corrections on a case-by-case basis,"
117+
)
118+
print("or to create model-specific converters.")
119+
120+
121+
print("--- Scenario 3: Correcting XXZChain DMRG state with X-gates ---")
122+
123+
L = 30
124+
xxz_model_params = {"L": L, "bc_MPS": "finite", "Jxx": 1.0, "Jz": 1.0, "conserve": None}
125+
xxz_M = XXZChain(xxz_model_params)
126+
psi0_xxz = MPS.from_product_state(
127+
xxz_M.lat.mps_sites(), ["up", "down"] * (L // 2), bc=xxz_M.lat.bc_MPS
128+
)
129+
dmrg_params = {"max_sweeps": 10, "trunc_params": {"chi_max": 64}}
130+
eng = dmrg.TwoSiteDMRGEngine(psi0_xxz, xxz_M, dmrg_params)
131+
E, psi_gs_xxz = eng.run()
132+
print(f"XXZ DMRG finished. Ground state energy: {E:.10f}")
133+
134+
state_raw_quvector = tc.quantum.tenpy2qop(psi_gs_xxz)
135+
136+
i, j = L // 2 - 1, L // 2
137+
corr_tenpy = psi_gs_xxz.correlation_function("Sz", "Sz", sites1=[i], sites2=[j])[0, 0]
138+
print("\nApplying X-gate to each qubit to correct the basis convention...")
139+
circuit_to_be_corrected = tc.MPSCircuit(L, wavefunction=state_raw_quvector)
140+
141+
for k in range(L):
142+
circuit_to_be_corrected.x(k)
143+
144+
corr_tc_corrected = (
145+
tc.backend.real(
146+
circuit_to_be_corrected.expectation((tc.gates.z(), [i]), (tc.gates.z(), [j]))
147+
)
148+
/ 4.0
149+
)
150+
151+
print(f"\nComparing <Sz_{i}Sz_{j}> correlation function for the DMRG ground state:")
152+
print(f"TeNPy (Ground Truth): {corr_tenpy:.8f}")
153+
print(f"TC (after X-gate correction): {corr_tc_corrected:.8f}")
154+
np.testing.assert_allclose(corr_tenpy, corr_tc_corrected, atol=1e-5)
155+
print(
156+
"\n[SUCCESS] The correlation functions match perfectly after applying the X-gate correction."
157+
)
158+
print(
159+
"This demonstrates the recommended physical approach to handle the XXZChain's inverted basis convention."
160+
)
161+
162+
163+
print("\n\nWorkflow demonstration and analysis complete!")

requirements/requirements-extra.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ qiskit-nature
55
mitiq
66
cirq
77
torch==2.2.2
8+
physics-tenpy==1.0.6
9+
quimb==1.11.1
810
# jupyter
911
mthree==1.1.0
1012
openfermion

tensorcircuit/mpscircuit.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@
44

55
# pylint: disable=invalid-name
66

7-
from functools import reduce
7+
from functools import reduce, partial
88
from typing import Any, List, Optional, Sequence, Tuple, Dict, Union
99
from copy import copy
10+
import logging
1011

1112
import numpy as np
1213
import tensornetwork as tn
13-
from tensorcircuit.quantum import QuOperator, QuVector
1414

1515
from . import gates
1616
from .cons import backend, npdtype, contractor, rdtypestr, dtypestr
17+
from .quantum import QuOperator, QuVector, extract_tensors_from_qop
1718
from .mps_base import FiniteMPS
1819
from .abstractcircuit import AbstractCircuit
20+
from .utils import arg_alias
1921

2022
Gate = gates.Gate
2123
Tensor = Any
24+
logger = logging.getLogger(__name__)
2225

2326

2427
def split_tensor(
@@ -77,6 +80,10 @@ class MPSCircuit(AbstractCircuit):
7780

7881
is_mps = True
7982

83+
@partial(
84+
arg_alias,
85+
alias_dict={"wavefunction": ["inputs"]},
86+
)
8087
def __init__(
8188
self,
8289
nqubits: int,
@@ -118,8 +125,19 @@ def __init__(
118125
), "tensors and wavefunction cannot be used at input simutaneously"
119126
# TODO(@SUSYUSTC): find better way to address QuVector
120127
if isinstance(wavefunction, QuVector):
121-
wavefunction = wavefunction.eval()
122-
tensors = self.wavefunction_to_tensors(wavefunction, split=self.split)
128+
try:
129+
nodes, is_mps, _ = extract_tensors_from_qop(wavefunction)
130+
if not is_mps:
131+
raise ValueError("wavefunction is not a valid MPS")
132+
tensors = [node.tensor for node in nodes]
133+
except ValueError as e:
134+
logger.warning(repr(e))
135+
wavefunction = wavefunction.eval()
136+
tensors = self.wavefunction_to_tensors(
137+
wavefunction, split=self.split
138+
)
139+
else: # full wavefunction
140+
tensors = self.wavefunction_to_tensors(wavefunction, split=self.split)
123141
assert len(tensors) == nqubits
124142
self._mps = FiniteMPS(tensors, canonicalize=False)
125143
self._mps.center_position = 0

0 commit comments

Comments
 (0)