Skip to content

Ground-state algorithms

The examples on this page use MPSKit.jl, MPSKitModels.jl, TensorKit.jl, and TensorKitTensors.jl. See Installation for how to add these packages to your environment.

find_groundstate is the single entry point for optimizing an MPS towards the ground state of a Hamiltonian. This page shows how to pick and configure the algorithm it runs, for both finite and infinite systems. For what each algorithm actually does and why you would choose one over another, see Ground-state algorithms. All examples share a single namespace:

julia
using MPSKit, MPSKitModels, TensorKit
using TensorKitTensors.SpinOperators: σˣ, σᶻ

1. Get a ground state with defaults

Called with just a state and a Hamiltonian, find_groundstate inspects the type of the initial state and picks a matching algorithm for you. For a FiniteMPS it runs DMRG with the keywords you pass through (tol, maxiter, verbosity):

julia
L = 8
ψ₀ = FiniteMPS(L, ℂ^2, ℂ^8)
H = transverse_field_ising(FiniteChain(L); g = 0.5)

ψ, envs, ϵ = find_groundstate(ψ₀, H; tol = 1.0e-8, maxiter = 50, verbosity = 0)
ϵ
5.67656253033246e-10

For an InfiniteMPS it instead runs VUMPS, and if the requested tol is tighter than 1e-4 it chains a GradientGrassmann pass afterwards to polish the last few digits (see §4):

julia
ψ₀_inf = InfiniteMPS(ℂ^2, ℂ^6)
H_inf = transverse_field_ising(; g = 0.5)

ψ_inf, envs_inf, ϵ_inf = find_groundstate(ψ₀_inf, H_inf; verbosity = 0)
ϵ_inf
4.756813901412719e-11

Passing a trscheme keyword switches on a two-site pre-pass that can grow the bond dimension before the single-site algorithm takes over. On a FiniteMPS this prepends DMRG2; on an InfiniteMPS it prepends IDMRG2 (which needs a unit cell of at least two sites, see §3).

julia
ψ_auto, envs_auto, ϵ_auto = find_groundstate(
    ψ₀, H;
    trscheme = truncrank(16), verbosity = 0
)
ϵ_auto
1.8990707477130713e-14

When to reach for an explicit algorithm

The keyword form above covers the common cases. Reach for an explicit algorithm struct (DMRG, DMRG2, VUMPS, IDMRG, IDMRG2, GradientGrassmann), or a chain of them with &, whenever you need finer control than the heuristic provides — the rest of this page shows how.


2. Configure finite-system DMRG

Pass a DMRG struct explicitly to set tol, maxiter, and verbosity directly:

julia
ψ_dmrg, envs_dmrg, ϵ_dmrg = find_groundstate(
    ψ₀, H,
    DMRG(; tol = 1.0e-8, maxiter = 50, verbosity = 0)
)
ϵ_dmrg
5.67656253033246e-10

DMRG updates one site at a time, so with its defaults it cannot change the bond dimension: whatever bond dimension ψ₀ starts with is what it keeps. <!– REVIEW: confirm the claim that single-site optimization also makes it harder for DMRG to find a good distribution of (symmetric) charges across the bond, versus DMRG2's two-site update. –> DMRG2 optimizes two sites at once and truncates back down, which lets it grow (or shrink) the bond dimension as it sweeps, at extra cost per step. Unlike DMRG, DMRG2 has no default truncation scheme, so trscheme is required:

julia
ψ_dmrg2, envs_dmrg2, ϵ_dmrg2 = find_groundstate(
    ψ₀, H,
    DMRG2(; trscheme = truncrank(16), maxiter = 5, verbosity = 0)
)
ϵ_dmrg2
4.440892098500626e-16

A common pattern is to warm up with DMRG2 to grow the bond dimension, then refine with the cheaper single-site DMRG. The & chaining operator runs the first algorithm to completion, then feeds its result into the second:

julia
warmup_then_refine = DMRG2(; trscheme = truncrank(16), maxiter = 3, verbosity = 0) &
    DMRG(; tol = 1.0e-8, maxiter = 30, verbosity = 0)

ψ_c, envs_c, ϵ_c = find_groundstate(ψ₀, H, warmup_then_refine)
ϵ_c
2.7653855808494097e-10

For more on choosing trscheme and growing bond dimension in general, see Controlling bond dimension.


3. Configure infinite-system algorithms

VUMPS is the default single-site algorithm for an InfiniteMPS, and takes the same tol/maxiter/verbosity keywords:

julia
ψ_v, envs_v, ϵ_v = find_groundstate(
    ψ₀_inf, H_inf,
    VUMPS(; tol = 1.0e-8, maxiter = 50, verbosity = 0)
)
ϵ_v
6.4156285953543825e-9

<!– REVIEW: confirm that VUMPS only works when the target state is injective (unique ground state), and that it should not be used when the ground state is degenerate/symmetry-broken at the given bond dimension. –> IDMRG is the infinite analogue of DMRG: it grows the system by repeatedly inserting sites in the middle and re-optimizing, until boundary effects wash out.

julia
ψ_i, envs_i, ϵ_i = find_groundstate(
    ψ₀_inf, H_inf,
    IDMRG(; tol = 1.0e-8, maxiter = 50, verbosity = 0)
)
ϵ_i
2.9766339681525525e-9

<!– REVIEW: confirm that IDMRG can converge slowly for critical (long correlation-length) systems, since the number of inserted sites needs to exceed the correlation length before boundary effects disappear. –> <!– REVIEW: confirm that VUMPS typically converges faster than IDMRG because a local update is immediately felt throughout the whole (infinite) system via the re-gauging step, rather than only at the newly inserted sites. –> In practice, prefer VUMPS unless you specifically need IDMRG's ability to change the bond dimension one site at a time.

IDMRG2 is the two-site, bond-dimension-changing variant, and mirrors DMRG2: trscheme is required, and it needs a unit cell of at least two sites.

Unit cell size

IDMRG2 throws an ArgumentError on a single-site InfiniteMPS. Build the initial state and Hamiltonian with a unit cell of two (or more) sites instead.

julia
J = 1.0
g = 0.5
X = σˣ()
Z = σᶻ()

lattice_2 = PeriodicVector([ℂ^2, ℂ^2])
H_inf_2 = InfiniteMPOHamiltonian(
    lattice_2,
    (1, 2) => -J * X  X,
    (2, 3) => -J * X  X,
    (1,) => -g * Z,
    (2,) => -g * Z,
)
ψ₀_2 = InfiniteMPS([ℂ^2, ℂ^2], [ℂ^2, ℂ^2])

ψ_i2, envs_i2, ϵ_i2 = find_groundstate(
    ψ₀_2, H_inf_2,
    IDMRG2(; trscheme = truncrank(16), maxiter = 5, verbosity = 0)
)
ϵ_i2
3.822566998848425e-12

4. Refine convergence with GradientGrassmann

GradientGrassmann performs Riemannian gradient descent directly on the manifold of (finite or infinite) MPS, using an optimizer from OptimKit (ConjugateGradient by default via the method keyword). <!– REVIEW: confirm the claim that gradient descent tends to have the best convergence rate close to the optimum ("in the tail"), so the fastest overall strategy is often a cheaper algorithm (DMRG/VUMPS/IDMRG) far from convergence, switched to GradientGrassmann once close. –> Chain it after VUMPS (or DMRG) with & to combine both regimes in one call — this is exactly what find_groundstate's heuristic does once tol is tighter than 1e-4:

julia
refine = VUMPS(; tol = 1.0e-6, maxiter = 20, verbosity = 0) &
    GradientGrassmann(; tol = 1.0e-10, maxiter = 50, verbosity = 0)

ψ_g, envs_g, ϵ_g = find_groundstate(ψ₀_inf, H_inf, refine)
ϵ_g
6.021804682714972e-11

Since GradientGrassmann is also a single-site algorithm, it cannot change the bond dimension either: grow it beforehand with DMRG2/IDMRG2 or the changebonds recipes in Controlling bond dimension.


5. Control output and tolerances

Every algorithm accepts a verbosity keyword as a plain integer:

verbosityOutput
0nothing
1warnings only
2convergence information
3per-iteration information (the default)
4everything

find_groundstate returns (ψ, envs, ϵ). ϵ is the final convergence-error measure (a Galerkin residual) of whichever algorithm ran last — it quantifies how well the sweeps converged, not the error in the energy itself.

The optional third positional argument to find_groundstate lets you reuse envs from a previous call instead of recomputing it, which is useful when tightening the tolerance on a state you already optimized:

julia
ψ_v2, envs_v2, ϵ_v2 = find_groundstate(
    ψ_v, H_inf,
    VUMPS(; tol = 1.0e-10, maxiter = 50, verbosity = 0),
    envs_v
)
ϵ_v2
5.646797745414425e-11

Where to go next

For growing, shrinking, and inspecting bond dimension during or between these calculations, see Controlling bond dimension. For background on when each algorithm applies and how it relates to the others, see Ground-state algorithms. To see find_groundstate used end to end on a finite chain, start from Your first ground state; for the infinite-system counterpart, see The thermodynamic limit.