Heisenberg Chain¶
Ground-state DMRG for the spin-½ Heisenberg chain.
\[
H = J \sum_{\langle i,j \rangle} \mathbf{S}_i \cdot \mathbf{S}_j, \quad J = 1
\]
The exact ground-state energy per site in the thermodynamic limit (Bethe ansatz) is \(E_\infty / L = \tfrac{1}{4} - \ln 2 \approx -0.4431\).
1. Setup¶
import alice
alice.configure_logging() # optional: write diagnostics to .logging/
from alice import build_interaction, build_hamiltonian, MPS, dmrg
2. Build the Hamiltonian from TOML¶
Save the following as heisenberg.toml:
[model.geometry]
lattice = "chain"
lx = 20
bcx = "OBC"
n2x = true
[model.model]
category = "bosonic"
label = "Heisenberg"
symmetry = "U1"
spin = 0.5
J = 1.0
Then load it:
interactions, spc, L = build_interaction("heisenberg.toml")
hamiltonian = build_hamiltonian(interactions, L, spc)
print(f"L = {L}, MPO bond dims: {hamiltonian.bond_dims}")
Or equivalently, pass a config dict directly (no file needed):
config = {
"geometry": {"lattice": "chain", "lx": 20, "bcx": "OBC", "n2x": True},
"model": {"category": "bosonic", "label": "Heisenberg",
"symmetry": "U1", "spin": 0.5, "J": 1.0},
}
interactions, spc, L = build_interaction(config)
hamiltonian = build_hamiltonian(interactions, L, spc)
3. Build an initial MPS¶
Alice does not include a general MPS initializer in its public API (use the examples or your own initialization). Here we build a simple Néel-state product MPS:
from nicole import isometry, Direction
from nicole.index import Index, Sector
ndigits = max(2, len(str(L)))
bond_trivial_out = Index([Sector(0, 1)], Direction.OUT)
bond_trivial_in = bond_trivial_out.flip()
tensors = []
for i in range(L):
# Alternate up (charge +1) and down (charge -1) to target Sz=0
charge = 1 if i % 2 == 0 else -1
phys = Index([Sector(charge, 1)], Direction.IN)
t = isometry(bond_trivial_out.flip(), phys)
t.insert_index(1, direction=Direction.OUT, itag=f'A{i+1:0{ndigits}d}')
t.retag(0, f'A{i:0{ndigits}d}')
t.retag(2, f's{i:02d}')
tensors.append(t)
mps = MPS(tensors)
4. Run DMRG¶
opts = dmrg.Options(
scheme = '2s', # 2-site update with SVD bond growth
n_sweeps = 20,
max_bond = 64,
e_tol = 1e-8,
)
summary = dmrg.run(mps, hamiltonian, opts)
print(f"Energy: {summary.energy:.10f}")
print(f"Energy/site: {summary.energy / L:.10f}")
print(f"Converged: {summary.converged}")
print(f"Sweeps done: {summary.n_sweeps}")
print(f"Max bond dim: {max(summary.bond_dims)}")
Expected output for L=20, max_bond=64:
5. SU(2) symmetry¶
To use full SU(2) spin-rotation symmetry, change symmetry to "SU2":
config["model"]["symmetry"] = "SU2"
interactions, spc, L = build_interaction(config)
hamiltonian = build_hamiltonian(interactions, L, spc)
With SU(2), the bond dimension in the symmetry-resolved basis is much smaller for the same physical accuracy. The DMRG run is identical.
6. Save and reload¶
import torch
torch.save(summary.serialize(), "heisenberg_result.pt")
# Later:
data = torch.load("heisenberg_result.pt", weights_only=True)
summary = dmrg.Summary.deserialize(data)
print(f"Reloaded energy: {summary.energy:.10f}")