Skip to content

\(S=\frac12\) chain: Symmetries and Ground State Correlators

Author: Paul Ebert

This example demonstrates how ground state expectation values can be computed for the spin-\(\frac{1}{2}\) XXX antiferromagnetic chain

\[ \mathcal{H} = \sum_{\langle i, j \rangle} \bm{S}_i \cdot \bm{S}_j = \sum_{\langle i, j \rangle} (S^x_i S^x_j + S^y_i S^y_j + S^z_i S^z_j) \]

with periodic boundary conditions. The Hamiltonian is invariant under translations and decomposes into \(N\) irreducible representations (irreps) labelled by lattice momentum \(2\pi/N \times k\) where \(k=0, 1, \ldots, N-1\). The Lanczos algorithm is run on all momentum sub-blocks, effectively exchanging a longer runtime for less memory consumption. An important caveat when working with representations is that the operators inside expectation values must be symmetrized with respect to the symmetry group.

#include <xdiag/all.hpp>

using namespace xdiag;

std::complex<double> C_N_character(int N, int k, int p) {
  return std::exp(((2 * k * p) * M_PI / (double)N) *
                  std::complex<double>(0, 1.));
}

int main() try {
  // periodic N-site Heisenberg antiferromagnet
  int N = 14;
  auto H = OpSum();
  for (int i = 0; i < N; i++) {
    H += "J" * Op("SdotS", {i, (i + 1) % N});
  }
  H["J"] = 1.0;

  // define shift by one site
  std::vector<int64_t> T_perm(N, 0);
  for (int i = 0; i < N - 1; i++) {
    T_perm[i] = i + 1;
  }
  auto T = Permutation(T_perm);

  // define cyclic group
  std::vector<Permutation> perms(N);
  for (int i = 0; i < N; i++) {
    perms[i] = pow(T, i);
  }
  auto C_N = PermutationGroup(perms);

  // define irreps from character tables, labelled by momentum (2pi/N ×) k
  std::vector<Representation> irreps(N);
  std::vector<std::complex<double>> characters(N, 0.0);
  for (int k = 0; k < N; k++) {
    for (int p = 0; p < N; p++) {
      characters[p] = C_N_character(N, k, p);
    };
    irreps[k] = Representation(C_N, characters);
  }

  // check all translation symmetry blocks
  double e0 = 0.0;
  State psi0;
  for (int k = 0; k < N; k++) {
    auto block = Spinhalf(N, irreps[k]);
    auto [e0k, psik] = eig0(H, block);
    if (e0k < e0) {
      e0 = e0k;
      psi0 = psik;
    }
  }

  // we measure the correlation (which needs to besymmetrized)
  auto corr_op = Op("SdotS", {0, N / 2 - 1});
  auto corr = inner(symmetrize(corr_op, C_N), psi0);

  Log("Ground state energy: {:.12f}", e0);
  Log("Ground state correlator: {:.12f}", corr);
} catch (Error e) {
  error_trace(e);
}
using XDiag

function main()

    # periodic N-site Heisenberg antiferromagnet
    N = 14
    H = OpSum()
    for i in 1:N
        H += "J" * Op("SdotS", [i, mod1(i+1, N)])
    end
    H["J"] = 1.0 

    # define spin correlator at "half-chain" distance
    corr_op = OpSum()
    corr_op += Op("SdotS", [1, N ÷ 2])

    # define shift by one site
    T = Permutation(circshift(1:N, -1))

    # define cyclic group
    C_N = PermutationGroup([T^k for k in 0:(N-1)])

    # define irreps from character tables, labelled by momentum (2pi/N ×) k
    character_table = [ [C_N_character(N, k, p) for p in 0:(N-1)] for k in 0:(N-1)]
    irreps = [ Representation(C_N, character_table[k]) for k in 1:N ]   

    # check all translation symmetry blocks
    e0 = Inf
    psi0 = nothing
    for k in eachindex(irreps)
        block = Spinhalf(N, irreps[k]) 
        e0k, psik = eig0(H, block)
        if  e0k < e0
            e0 = e0k
            psi0 = psik
        end
    end

    # now the operator must be symmetrized!
    corr = inner(symmetrize(corr_op, C_N), psi0)

    println("Ground state energy: $e0")
    println("Ground state correlator: $corr")
    println("State vector length = ", size(psi0))

end

function C_N_character(N::Int, k::Int, p::Int)
    exp(im * 2 * pi * k * p / N)
end

main()