Hubbard Model¶
Ground-state DMRG for the 1D Hubbard model with U(1)×SU(2) symmetry.
At half-filling (µ = 0) the Hubbard model is a Mott insulator for any U > 0. Its ground-state energy is known exactly via the Bethe ansatz.
1. Setup¶
import alice
alice.configure_logging()
from alice import build_interaction, build_hamiltonian, MPS, dmrg
2. Build the Hamiltonian¶
U(1)×SU(2) symmetry conserves total particle number and total spin simultaneously, achieving the best compression ratio:
config = {
"geometry": {"lattice": "chain", "lx": 8, "bcx": "OBC", "n2x": True},
"model": {"category": "conductor", "label": "Hubbard",
"symmetry": "U1,SU2", "t": 1.0, "U": 4.0, "mu": 0.0},
}
interactions, spc, L = build_interaction(config)
hamiltonian = build_hamiltonian(interactions, L, spc)
print(f"L = {L}, MPO bond dims: {hamiltonian.bond_dims}")
Alternatively, use U(1)×U(1) to conserve spin-up and spin-down counts separately:
3. Build an initial MPS¶
For U(1)×SU(2), the product-state initialization targets the half-filled singlet sector (total particle number L, total spin S=0). Building the initial MPS correctly for non-Abelian symmetry is non-trivial; it is typically done via iterative diagonalization (see examples/examples_dmrg/dmrg_heisenberg.py for a reference implementation).
A simpler approach is to start from a 2-site DMRG sweep with a small random MPS:
# For a demonstration, use the 2-site scheme to grow bonds from a product state.
opts = dmrg.Options(
scheme = '2s',
n_sweeps = 30,
max_bond = 128,
e_tol = 1e-8,
)
4. Run DMRG¶
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"Max bond dim: {max(summary.bond_dims)}")
For L=8, U=4, t=1, OBC, the ground-state energy is approximately E ≈ -3.67.
5. Environment caching for large chains¶
For long chains where all environment blocks do not fit in RAM, enable disk caching:
opts = dmrg.Options(
scheme = '2s',
n_sweeps = 20,
max_bond = 256,
env_cache_dir = "/tmp/alice_envs", # write blocks to disk
env_window = 4, # keep 4 blocks in memory
env_async_io = True, # overlap I/O with computation
)
The env_cache_dir directory is created automatically. Each run then creates its own uniquely-named subdirectory inside it, so several jobs dispatched to the same node can share one cache root without overwriting each other's blocks. Individual blocks are stored as .pt files inside that subdirectory and re-loaded transparently as the sweep progresses; the subdirectory is removed when the run finishes or raises, leaving env_cache_dir itself empty.