Skip to content

Commit b442315

Browse files
committed
Add examples/MpsMagnetization: 2D ITF Trotter MPS evolution
Includes helper.py and mps_simulator.ipynb (outputs stripped) demonstrating pepsy.MpsOptimizer for 2D transverse-field Ising Trotter evolution with multiple modes (mpo / dmrg / svd / swap / exact) and CuPy/Torch backends.
1 parent 4868220 commit b442315

2 files changed

Lines changed: 650 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""Helper functions for the MPS simulator notebook."""
2+
3+
import math
4+
import numpy as np
5+
import quimb
6+
import quimb.tensor as qtn
7+
import pepsy as py
8+
9+
10+
def build_lattice(Lx, Ly, coupling_j, field_h, cyclic=True, lattice="square", mode="hilbert"):
11+
"""Build the 2D ITF lattice: mapper, Hamiltonian MPO, edges, sites.
12+
13+
Returns
14+
-------
15+
dict with keys: mpo_H, edges_1d, sites, mapper, res (full build_itf_lattice output).
16+
"""
17+
mapper = py.core.OneDMap(Lx, Ly, mode="hilbert")
18+
res = py.ham_tn.build_itf_lattice(
19+
L_x=Lx, L_y=Ly, lattice=lattice, cyclic=cyclic,
20+
J=coupling_j, field=field_h,
21+
mapper=mapper,
22+
)
23+
mpo_H = res["mpo"]
24+
edges_1d = res["edges_1d"]
25+
sites = sorted({(site,) for edge in edges_1d for site in edge})
26+
return {
27+
"mpo_H": mpo_H,
28+
"edges_1d": edges_1d,
29+
"sites": sites,
30+
"mapper": mapper,
31+
"res": res,
32+
}
33+
34+
35+
def build_initial_state(L, coupling_j, field_h, z_coord=4, theta_offset=None):
36+
"""Build the intermediate-temperature product state (arXiv:2503.20870).
37+
38+
Parameters
39+
----------
40+
theta_offset : float or None
41+
Offset added to arcsin(h/(J·z)). Default is 2π/9 (paper value).
42+
43+
Returns
44+
-------
45+
psi0 : quimb.tensor.MatrixProductState
46+
theta_paper : float
47+
"""
48+
if theta_offset is None:
49+
theta_offset = 2 * math.pi / 9
50+
ratio = field_h / (coupling_j * z_coord)
51+
sin2theta = np.clip(ratio, -1.0, 1.0)
52+
theta_min = np.arcsin(sin2theta)
53+
theta_paper = theta_min + theta_offset
54+
psi0 = py.ps_to_mps(L, theta=0.5 * theta_paper)
55+
return psi0, theta_paper
56+
57+
58+
def build_trotter_gates(sites, edges_1d, field_h, coupling_j, dt, to_backend):
59+
"""Build 2nd-order Strang-splitting Trotter gate stream.
60+
61+
RX(h·dt) → RZZ(2J·dt) → RX(h·dt)
62+
63+
Convention: py.rx(θ) = exp(-i θ X / 2), so the half-step
64+
exp(-i h·dt/2 · X) requires rx(h·dt).
65+
66+
Returns
67+
-------
68+
list of (gate, where) tuples.
69+
"""
70+
rx_half = to_backend(py.rx(field_h * dt))
71+
rzz_full = to_backend(py.rzz(2 * coupling_j * dt))
72+
73+
gates = []
74+
for site in sites:
75+
gates.append((rx_half, site))
76+
for edge in edges_1d:
77+
gates.append((rzz_full, edge))
78+
for site in sites:
79+
gates.append((rx_half, site))
80+
return gates
81+
82+
83+
def build_mpo_z_sq(Lx, Ly, mapper):
84+
"""Build the (ΣZ_i/L)² MPO for measuring magnetization squared.
85+
86+
Returns
87+
-------
88+
mpo_z_sq_offdiag : quimb.tensor.MatrixProductOperator
89+
Off-diagonal part: (1/L²) Σ_{i≠j} Z_i Z_j
90+
diagonal_shift : float
91+
Constant 1/L to add (from diagonal Z_i²=I terms).
92+
mpo_z : quimb.tensor.MatrixProductOperator
93+
The magnetization MPO M = (1/L) Σ Z_i (useful on its own).
94+
"""
95+
L = Lx * Ly
96+
Z_op = quimb.pauli("Z", dtype="complex128")
97+
98+
builder = py.ham_tn(Lx=Lx, Ly=Ly, mapper=mapper, data_type="complex128")
99+
100+
# Sites in 2D coordinates (what build_mpo expects)
101+
res = py.ham_tn.build_itf_lattice(
102+
L_x=Lx, L_y=Ly, lattice="square", cyclic=True,
103+
J=-1, field=1, mapper=mapper,
104+
)
105+
all_sites_2d = list(res["one_d_to_two_d"].values())
106+
107+
# M = (1/L) Σ_i Z_i
108+
z_terms = [((Z_op,), (site,), 1.0 / L) for site in all_sites_2d]
109+
mpo_z = builder.build_mpo(z_terms, compress_each=True)
110+
111+
# M² off-diagonal = (1/L²) Σ_{i≠j} Z_i Z_j
112+
z_sq_terms = []
113+
for i, site_i in enumerate(all_sites_2d):
114+
for j, site_j in enumerate(all_sites_2d):
115+
if i != j:
116+
z_sq_terms.append(
117+
((Z_op, Z_op), (site_i, site_j), 1.0 / (L * L))
118+
)
119+
120+
mpo_z_sq_offdiag = builder.build_mpo(z_sq_terms, compress_each=True)
121+
diagonal_shift = 1.0 / L # from Z_i² = I
122+
123+
return mpo_z_sq_offdiag, diagonal_shift, mpo_z
124+
125+
126+
def measure_energy(psi, mpo_H, L, optimizer):
127+
"""<H>/L via MPO."""
128+
e = py.core.expec_mpo(mpo_H, psi, contraction_opt=optimizer)
129+
return float(np.real(e)) / L
130+
131+
132+
def measure_z(psi, mpo_z, optimizer):
133+
"""<ΣZ/L> = <M> via MPO."""
134+
m = py.core.expec_mpo(mpo_z, psi, contraction_opt=optimizer)
135+
return float(np.real(m))
136+
137+
138+
def measure_z_sq(psi, mpo_z_sq_offdiag, diagonal_shift, optimizer):
139+
"""<(ΣZ/L)²> = <M²> via MPO + diagonal shift."""
140+
off_diag = py.core.expec_mpo(mpo_z_sq_offdiag, psi, contraction_opt=optimizer)
141+
return float(np.real(off_diag)) + diagonal_shift

0 commit comments

Comments
 (0)