Skip to content

Symmetries

TensorKit for MPS users already showed the key fact: a symmetry in MPSKit is not a flag passed to an algorithm, it is a property of the vector spaces that a tensor is built from, and every algorithm is written once, generically, for any such space. That page built the mental model of a TensorMap and introduced graded spaces through a single ℤ₂ example. This page stays at the same level of abstraction but widens the lens: what kinds of symmetry a graded space can encode, what changes qualitatively as you move from an abelian group to a non-abelian one or to fermionic or anyonic statistics, and — the question every user eventually asks — when the extra bookkeeping of a bigger symmetry group is actually worth it. For the hands-on version of the ℤ₂ case worked all the way through a ground-state search, see Using symmetries; this page explains the reasoning behind that recipe and extends it to the other symmetry classes MPSKit supports.

As on the sibling page, none of the symmetry machinery lives in MPSKit or MPSKitModels itself: using MPSKit does not bring a single sector or space type into scope, and neither does using MPSKitModels. Every symmetric object — Z2Irrep, U1Irrep, SU2Irrep, Z2Space, U1Space, and so on — comes from TensorKit, so every example on this page loads all three packages explicitly.

julia
using MPSKit, MPSKitModels, TensorKit

Sectors, charges, and block-sparsity

A sector is a label for an irreducible representation of the symmetry: for a group symmetry it is one irrep, and a graded space is built by declaring how many copies ("degeneracy" or "multiplicity") of each sector it contains. TensorKit for MPS users did this for ℤ₂ with Z2Space(0 => 1, 1 => 1); the same sector => degeneracy syntax works for every symmetry, so a U(1)-graded space that keeps track of, say, a conserved particle number or magnetization from -1 to 1 reads:

julia
V = U1Space(-1 => 1, 0 => 1, 1 => 1)
dim(V)
3

The space still knows its full sector content, queryable with sectors and dim applied to a specific sector:

julia
collect(sectors(V))
3-element Vector{TensorKitSectors.U1Irrep}:
 0
 1
 -1
julia
[dim(V, c) for c in sectors(V)]
3-element Vector{Int64}:
 1
 1
 1

Charge conservation is the statement that a symmetric tensor may only have nonzero entries between sectors whose charges add up correctly (for a Hamiltonian term, incoming and outgoing charge must match). Concretely this means the tensor is block-diagonal in the sector label: what would be one dense array for a plain ℂ^n space becomes a handful of smaller, independent dense blocks, one per allowed sector combination, and the entries that connect different sectors are not merely zero — they are never allocated or touched at all. This is the mechanism behind everything that follows: the type of symmetry only changes what the sector labels are and how they combine (their fusion rules); the block-sparse storage and the charge-conservation bookkeeping are handled identically underneath.

A taxonomy of symmetry types

MPSKitModels' heisenberg_XXX model is a convenient single thread through the taxonomy, because the same Heisenberg Hamiltonian can be built with a trivial symmetry or with any of the three main non-trivial types below, purely by passing a different sector type as the first argument:

julia
H_triv = heisenberg_XXX(FiniteChain(4); spin = 1 // 2)
H_Z2 = heisenberg_XXX(Z2Irrep, FiniteChain(4); spin = 1 // 2)
H_U1 = heisenberg_XXX(U1Irrep, FiniteChain(4); spin = 1 // 2)
H_SU2 = heisenberg_XXX(SU2Irrep, FiniteChain(4); spin = 1 // 2)
4-site FiniteMPOHamiltonian(ComplexF64, Rep[SU₂]) with maximal dimension 5:
┬─[4]─ (1/2 => 1)
│ ((0=>1) ⊞ (1=>1) ⊞ (0=>1))
┼─[3]─ (1/2 => 1)
│ ((0=>1) ⊞ (1=>1) ⊞ (0=>1))
┼─[2]─ (1/2 => 1)
│ ((0=>1) ⊞ (1=>1) ⊞ (0=>1))
┴─[1]─ (1/2 => 1)

Four Hamiltonians, four different tensor structures, one physical model.

Abelian symmetries: ℤ_N and U(1)

Z2Irrep, Z3Irrep, Z4Irrep, and the general ZNIrrep, together with U1Irrep, are the abelian family: their sectors are literally the elements of ℤ_N or of the integers (or half-integers), and two sectors fuse by addition modulo N, or ordinary addition for U(1). Every irrep is one-dimensional, so an abelian symmetry buys exactly the block-sparsity described above and nothing more: H_Z2above encodes the same spin-flip parity used throughout [Using symmetries](@ref tutorial_using_symmetries), whileH_U1encodes conservation of total magnetizationS^z_{\mathrm{tot}}$, with sectors running over the possible values of$S^z_{\mathrm{tot}}.transverse_field_isingis a useful reminder that not every model has every symmetry available: it acceptsTrivial,Z2Irrep, orFermionParity, but raises an error forU1Irrep, because the transverse-field Ising model genuinely only has the ℤ₂ spin-flip symmetry — there is no conserved U(1) charge to exploit.

Non-abelian symmetries: SU(2)

SU2Irrep sectors are labelled by a total spin  , and fusing two of them follows the angular-momentum addition (Clebsch–Gordan) rule rather than simple addition: fusing spin and can produce any from   to  . The qualitative difference from the abelian case is that each sector is not one-dimensional but  -dimensional, and a symmetric tensor need only store the multiplicity of each — the internal   structure of every multiplet is fixed by representation theory and is never stored explicitly. H_SU2 above is built exactly this way: it is the same Heisenberg chain, only now every eigenstate additionally carries a total-spin label, and the tensors only ever store one number per multiplet rather than one number per individual magnetic sublevel. The next section makes this saving concrete.

Fermionic symmetries and product sectors

FermionParity grades a space into an even and an odd fermion-number sector, and — crucially — TensorKit's fermionic tensor category attaches the anticommutation sign directly to the braiding of FermionParity-graded legs, so that once physical and virtual legs carry this sector, index permutations automatically pick up the correct fermionic signs instead of requiring the sign rule to be implemented by hand in every algorithm (Mortier et al., 2025). Models with more than one physical species combine sectors with (typed \boxtimes) into a ProductSector, for instance an odd fermion paired with unit U(1) charge:

julia
FermionParity(1)  U1Irrep(1)
(FermionParity(1) ⊠ Irrep[U₁](1))

hubbard_model exercises this directly: it takes an independent particle symmetry and spin symmetry, and assembles the physical space internally out of FermionParity ⊠ (particle symmetry) and FermionParity ⊠ (spin symmetry) pieces. Choosing U(1) for particle number and SU(2) for spin gives the maximally symmetric Hubbard chain:

julia
H_hub = hubbard_model(ComplexF64, U1Irrep, SU2Irrep, FiniteChain(4); t = 1.0, U = 8.0)
4-site FiniteMPOHamiltonian(ComplexF64, Vect[(FermionParity ⊠ Irrep[U₁] ⊠ Irrep[SU₂])]) with maximal dimension 6:
┬─[4]─ ((0, 0, 0) => 1, (1, 1, 1/2) => 1, (0, 2, 0) => 1)
│ (((0, 0, 0)=>1) ⊞ ((1, 1, 1/2)=>1, (1, -1, 1/2)=>1) ⊞ ((0, 0, 0)=>1))
┼─[3]─ ((0, 0, 0) => 1, (1, 1, 1/2) => 1, (0, 2, 0) => 1)
│ (((0, 0, 0)=>1) ⊞ ((1, 1, 1/2)=>1, (1, -1, 1/2)=>1) ⊞ ((0, 0, 0)=>1))
┼─[2]─ ((0, 0, 0) => 1, (1, 1, 1/2) => 1, (0, 2, 0) => 1)
│ (((0, 0, 0)=>1) ⊞ ((1, 1, 1/2)=>1, (1, -1, 1/2)=>1) ⊞ ((0, 0, 0)=>1))
┴─[1]─ ((0, 0, 0) => 1, (1, 1, 1/2) => 1, (0, 2, 0) => 1)

The first argument is the scalar element type, required here because a lattice is given explicitly; the two symmetry types then set the particle-number and spin symmetries in that order.

By contrast, bose_hubbard_model only accepts Trivial or U1Irrep: bosons carry no parity grading, so there is no fermionic sign to encode and no spin degree of freedom to make non-abelian. At the far end of this spectrum, quantum_chemistry_hamiltonian does not expose a symmetry choice at all — it always builds its tensors with the fixed, maximal    symmetry (particle number, total spin, and fermionic sign), because for realistic molecular Hamiltonians that full symmetry is essentially always worth imposing.

Anyonic symmetries

The generality goes further than groups. Sectors such as FibonacciAnyon or IsingAnyon are not group representations at all — their fusion rules come from a modular tensor category — yet because every MPSKit algorithm is written against the abstract Sector interface, they are handled by exactly the same code paths, with no special-casing. The hard-hexagon model example puts this to work: its transfer matrix is built from FibonacciAnyon-graded tensors (Vect[FibonacciAnyon](:I => …, :τ => …)), and the standard statistical-mechanics workflow computes its partition function just as it would for an ordinary symmetry.

When does SU(2) pay off

Two distinct effects are at play whenever a symmetry is switched on, and it is worth separating them because only one of them scales with the size of the symmetry group.

The first effect is the block-sparsity already described: at a fixed total bond dimension, the computer multiplies several smaller dense blocks instead of one large one, and the (forbidden) cross-sector entries are never stored. Using symmetries demonstrates this concretely for ℤ₂: the same 16-dimensional bond becomes two roughly-8-dimensional blocks. This first effect is present for any symmetry, abelian or not, and its benefit grows with the number of distinct sectors the bond dimension gets spread over.

The second effect is specific to non-abelian symmetries and is qualitatively larger: because a whole  -dimensional multiplet is represented by a single stored block, the number of stored parameters needed to reach a given total, physical bond dimension shrinks. This can be checked directly: build a graded SU(2) space and compare its total dimension against the multiplicities it actually stores per sector.

julia
V_SU2 = SU2Space(0 => 2, 1 // 2 => 4)
dim(V_SU2)
10
julia
[dim(V_SU2, c) for c in sectors(V_SU2)]
2-element Vector{Int64}:
 2
 4

The total dimension dim(V_SU2) is 10, because each spin- sector contributes its  -fold multiplet:           . But dim(V_SU2, c) returns the stored multiplicity of each sector — here [2, 4], just six numbers in total — because the   internal structure of every multiplet is fixed by representation theory and never stored. For an abelian symmetry the two coincide (every irrep is one-dimensional, so the multiplicities and the total dimension agree, as with the U(1) space above); it is precisely for a non-abelian group that the stored count falls below the physical dimension.

The gap between the physical dimension (10) and the six numbers actually stored is the source of SU(2)'s reputation for letting DMRG reach much larger effective bond dimensions at the same computational cost — the same principle used to push non-abelian symmetric uniform MPS to large SU(3) bond dimensions in practice (Devos et al., 2022).

None of this is free. Every symmetric block carries the overhead of tracking fusion trees and recombining Clebsch–Gordan coefficients whenever legs are permuted or contracted, and for a non-abelian group this bookkeeping is genuinely more expensive per block than for an abelian one. In practice this means SU(2) (or any non-abelian symmetry) is worth reaching for when the physics genuinely has that symmetry — a spin chain with full rotational invariance, for instance — and when the bond dimension is large enough that the multiplet-reduction saving dominates the per-block overhead; for small bond dimensions, or for a symmetry the Hamiltonian does not actually have, the abelian or even trivial case is often simpler and just as fast.

Fixing the total charge

Sector labels are not only a storage optimization: they are physical quantum numbers, and MPSKit lets a calculation target a specific one directly.

For an MPS, the total charge is fixed by giving the state a non-trivial left or right virtual space, rather than the default unit (trivial-charge) one:

julia
ψ_odd = FiniteMPS(
    4, Z2Space(0 => 1, 1 => 1), Z2Space(0 => 2, 1 => 2);
    left = Z2Space(1 => 1)
)
left_virtualspace(ψ_odd, 1)
Rep[ℤ₂](…) of dim 1:
 1 => 1

Every tensor in ψ_odd is now forced, by charge conservation, to represent a state of odd total parity — there is no way for a symmetric MPS built this way to drift into the even sector. The same idea appears for excited states and for transfer-matrix spectra: the sector keyword of excitations and of transfer_spectrum restricts the search to a chosen total charge instead of the default trivial one, exactly as used to isolate the odd-parity excitation of the TFIM in Using symmetries. Excited states collects further recipes for working with sector, and Constructing states collects the analogous recipes for building states with a prescribed symmetry and charge.

Where to go next

<!– Maintainer notes (not rendered):

  • All @example blocks were executed end-to-end in one shared session against TensorKit v0.17.0 / MPSKitModels v0.4.7 (docs project). heisenberg_XXX needs an explicit spin = 1 // 2 (default spin = 1; Z2Irrep is spin-1/2 only), and hubbard_model needs an explicit ComplexF64 element type when a lattice is passed — both now reflected in the code above.

  • No bib entry directly supports the canonical SU(2)-specific non-abelian-DMRG scaling claim; devos2022 (SU(3)) is used as the closest available citation and flagged as such. Consider adding a citation for this if the maintainer has one on hand (e.g. a symmetric DMRG methods paper).

–>