Translation symmetry with OperatorTS

Here we will show how to take advantage of translation symmetry to save time and memory in PauliStrings.jl. Consider the 1D Ising Hamiltonian with periodic boundary conditions $H=-J(\sum_{i}Z_i Z_{i+1} +g \sum_i X_i)$. There is no need to actually store all the Pauli strings in this case. H is fully specified by just $-JZ_1Z_2$ and $-Jg X_1$ and the fact that it's translation symmetric.

In general, a 1D translation symmetric operator can be written as $\sum_i T_i H_0$ where $T_i$ is the i-sites translation operator and $H_0$ is the local operator that generates $H$. $H_0$ can be chosen so that it's only composed of Pauli strings that start on the first site.

In PauliStrings.jl, the structure OperatorTS lets you manipulate operators in this format. If your problem is translation symmetric, OperatorTS will be much faster than Operator.

Construction

The constructor of OperatorTS takes a normal operator and turns it into a lazy sum over all possible translations. There are two ways of using this constructor.

From the full translation symmetric $H$

First we construct the full Operator:

function ising1D(N, J, g)
    H = Operator(N)
    for j in 1:(N - 1)
        H += "Z",j,"Z",j+1
    end
    H += "Z",1,"Z",N #periodic boundary condition
    for j in 1:N
        H += g,"X",j
    end
    return -J*H
end
N = 4
J = -1.0
g = 1.0

H = ising1D(N, J, g)

then convert it to an OperatorTS

Hts = OperatorTS{(N,)}(H)/N

# output

(1.0 + 0.0im) 111X
(1.0 + 0.0im) 11ZZ

Note the normalization factor 1/N that normalizes the symmetric sum. We can use is_ts to check if H is actually symmetric.

From the local generator $H_0$

Construct $H_0$ using Operator:

H = Operator(N)
H += -J, "Z",1,"Z",2
H += -J*g,"X",1

then convert it to an OperatorTS

Hts = OperatorTS{(N,)}(H)

# output

(1.0 + 0.0im) 111X
(1.0 + 0.0im) 11ZZ

In this case, no normalization factor is needed.

Manipulation

All the operations defined on Operator are also defined on OperatorTS.

Construct a simple translation invariant operator on 4 sites:

H = Operator(N)
H += "X", 1
H += "Z", 1,"Z", 2

Hts = OperatorTS{(N,)}(H)

println(H)
println(resum(Hts))

# output

(1.0 + 0.0im) X111
(1.0 + 0.0im) ZZ11

(1.0 + 0.0im) X111
(1.0 + 0.0im) 1X11
(1.0 + 0.0im) 11X1
(1.0 + 0.0im) 111X
(1.0 + 0.0im) ZZ11
(1.0 + 0.0im) 1ZZ1
(1.0 + 0.0im) Z11Z
(1.0 + 0.0im) 11ZZ

Note that only the local generator is printed when printing OperatorTS, not the full operator.

Multiplication:

julia> Hts*Hts
(1.0 + 0.0im) 1X1X
(1.0 + 0.0im) ZZZZ
(2.0 + 0.0im) 1111
(2.0 + 0.0im) 11XX
(2.0 + 0.0im) 1Z1Z
(2.0 + 0.0im) X1ZZ
(2.0 + 0.0im) 1XZZ

Addition:

julia> Hts+Hts
(2.0 + 0.0im) 111X
(2.0 + 0.0im) 11ZZ

etc.

Example: computing $Tr(H^k)$

As an example of performance gains of using OperatorTS instead of Operator we compute the 8th moment ($Tr(H^8)$) of a 30 spin system.

Using the function defined above we construct a Ising Hamiltonian and convert it to an OperatorTS:

N = 30
k = 8
H = ising1D(N, J, g)
Hts = OperatorTS{(N,)}(H)/N

then we compute the kth moment (trace_product) using both Operator and OperatorTS :

julia> @time trace_product(H, k)
  0.688497 seconds (451 allocations: 135.925 MiB, 54.67% gc time)
1.1904927790006272e18 + 0.0im
julia> @time trace_product(Hts, k)
  0.038475 seconds (235 allocations: 7.023 MiB, 27.41% gc time)
1.1904927790006272e18 + 0.0im

OperatorTS is 18 times faster in this case.

Translation symmetry in 2D

Similar to the 1D case, a 2D translation symmetric operator can be constructed by either specifying an operator on each site, or by using a local generator. Here we construct a 2D transverse field ising model on a rectangular lattice with periodic boundary conditions with the help of the string_2d function:

function ising2D(L1, L2, g)
    H = Operator(L1 * L2)
    for x in 1:L1
        for y in 1:L2
            H += string_2d(("Z", x, y, "Z", x + 1, y), L1, L2, pbc=true) # horizontal
            H += string_2d(("Z", x, y, "Z", x, y + 1), L1, L2, pbc=true) # vertical
            H += g * string_2d(("X", x, y), L1, L2, pbc=true) # transverse field
        end
    end
    return H
end
L1 = 3
L2 = 2
julia> H = ising2D(L1, L2, 0.5)
(0.5 + 0.0im) X11111
(0.5 + 0.0im) 1X1111
(0.5 + 0.0im) 11X111
(0.5 + 0.0im) 111X11
(0.5 + 0.0im) 1111X1
(0.5 + 0.0im) 11111X
(1.0 + 0.0im) ZZ1111
(1.0 + 0.0im) Z1Z111
(1.0 + 0.0im) 1ZZ111
(1.0 + 0.0im) 111ZZ1
(1.0 + 0.0im) 111Z1Z
(1.0 + 0.0im) 1111ZZ
(2.0 + 0.0im) Z11Z11
(2.0 + 0.0im) 1Z11Z1
(2.0 + 0.0im) 11Z11Z

In general, if you have a lattice with extents $L_1, L_2$ in the $a_1$ and $a_2$ directions respectively, a PauliString is written in column-major order (similar to how a matrix is flattened in Julia), that is, $L_2$ concatenated chunks of length $L_1$ each.

To convert to an OperatorTS, in higher dimensions we have to specify the size of the lattice.

julia> Hts = OperatorTS{(L1, L2)}(H)/(L1*L2)
(0.5 + 0.0im) 111
              11X
(1.0 + 0.0im) 11Z
              11Z
(1.0 + 0.0im) 111
              1ZZ

Alternatively, you can also specify the local generator only:

H0 = Operator(L1 * L2)
H0 += 1.0 * string_2d(("Z", 1, 1, "Z", 2, 1), L1, L2)
H0 += 1.0 * string_2d(("Z", 1, 1, "Z", 1, 2), L1, L2)
H0 += 0.5 * string_2d(("X", 1, 1), L1, L2)
julia> Hts = OperatorTS{(L1,L2)}(H0)
(0.5 + 0.0im) 111
              11X
(1.0 + 0.0im) 11Z
              11Z
(1.0 + 0.0im) 111
              1ZZ