DMRG

The Density Matrix Renormalization Group (DMRG) finds the ground state of a 1D quantum Hamiltonian given as a Matrix Product Operator (MPO).

Background

DMRG variationally optimises a Matrix Product State (MPS) by sweeping through the chain and updating one or two site tensors at a time. At each step it solves a local eigenvalue problem (Lanczos) for the effective Hamiltonian projected into the MPS subspace, then truncates via SVD to control the bond dimension.

Key properties of the Tenax implementation:

  • 2-site and 1-site variants (DMRGConfig.two_site).

  • Outer sweep loop is a Python for-loop (bond dimensions change dynamically).

  • The effective Hamiltonian matvec is @jax.jit compiled.

  • Lanczos eigensolver uses a simple Python loop (suitable for moderate iteration counts).

Configuration

from tenax import DMRGConfig

config = DMRGConfig(
    max_bond_dim=64,       # maximum MPS bond dimension
    num_sweeps=30,         # full left-right sweep cycles
    convergence_tol=1e-10, # stop early when |dE| < tol
    two_site=True,         # 2-site DMRG (allows bond growth)
    lanczos_max_iter=50,   # Lanczos iteration cap
    lanczos_tol=1e-12,     # Lanczos convergence tolerance
    noise=0.0,             # density-matrix perturbation
    verbose=True,
)

Example – Heisenberg chain

from tenax import (
    DMRGConfig, dmrg,
    build_mpo_heisenberg, build_random_mps,
)

L = 20
mpo = build_mpo_heisenberg(L, Jz=1.0, Jxy=1.0, hz=0.0)
mps = build_random_mps(L, physical_dim=2, bond_dim=4)

config = DMRGConfig(max_bond_dim=64, num_sweeps=30, verbose=True)
result = dmrg(mpo, mps, config)

print(f"Energy:    {result.energy:.10f}")
print(f"Converged: {result.converged}")
print(f"Sweeps:    {len(result.energies_per_sweep)}")

Result object

dmrg() returns a DMRGResult named tuple:

Field

Type

Description

energy

float

Final ground-state energy

energies_per_sweep

list[float]

Energy at the end of each sweep

mps

TensorNetwork

Optimised MPS

truncation_errors

list[float]

Truncation error at each bond update

converged

bool

Whether energy converged within tolerance

MPO construction

build_mpo_heisenberg builds the standard 5-state MPO for the XXZ Heisenberg model:

\[H = J_z \sum_i S^z_i S^z_{i+1} + \frac{J_{xy}}{2} \sum_i (S^+_i S^-_{i+1} + S^-_i S^+_{i+1}) + h_z \sum_i S^z_i\]

For custom Hamiltonians, see the AutoMPO tutorial.

Example – 2D Heisenberg cylinder

For 2D systems, map the lattice to a 1D chain (column-major ordering) and use AutoMPO to build the long-range MPO:

from tenax import AutoMPO, DMRGConfig, build_random_mps, dmrg

Lx, Ly, N = 8, 4, 32
auto = AutoMPO(L=N, d=2)
for x in range(Lx):
    for y in range(Ly):
        # Within-ring bond (periodic y-direction)
        i, j = x * Ly + y, x * Ly + (y + 1) % Ly
        auto += (1.0, "Sz", min(i,j), "Sz", max(i,j))
        auto += (0.5, "Sp", min(i,j), "Sm", max(i,j))
        auto += (0.5, "Sm", min(i,j), "Sp", max(i,j))
        # Between-ring bond (open x-direction)
        if x < Lx - 1:
            i, j = x * Ly + y, (x + 1) * Ly + y
            auto += (1.0, "Sz", i, "Sz", j)
            auto += (0.5, "Sp", i, "Sm", j)
            auto += (0.5, "Sm", i, "Sp", j)

mpo = auto.to_mpo(compress=True)
mps = build_random_mps(N, physical_dim=2, bond_dim=16)
config = DMRGConfig(max_bond_dim=200, num_sweeps=15, verbose=True)
result = dmrg(mpo, mps, config)
print(f"E/N = {result.energy / N:.8f}")

See examples/heisenberg_cylinder.py for a complete working example with multiple cylinder sizes and exact-diagonalisation cross-checks.

Label conventions

MPS and MPO tensors follow these leg-label conventions:

MPS site tensors:

  • Left virtual bond: v{i-1}_{i}

  • Physical leg: p{i}

  • Right virtual bond: v{i}_{i+1}

  • Boundary sites omit one virtual bond

MPO site tensors:

  • Left MPO bond: w{i-1}_{i}

  • Top physical (ket): mpo_top_{i}

  • Bottom physical (bra): mpo_bot_{i}

  • Right MPO bond: w{i}_{i+1}