EditURL = "../../../../examples/2d_ising_partition_function/main.jl"
While PEPSKit has a lot of use in quantum systems, describing states using InfinitePEPS that can be contracted via CTMRG or [boundary MPS techniques](@ref e_boundary_mps), here we shift our focus to classical physics. We consider the 2D classical Ising model and compute its partition function defined as:
where the classical spins InfinitePartitionFunction object, as we will see.
But first, let's seed the RNG and import all required modules:
using Random, LinearAlgebra
using TensorKit, PEPSKit
using QuadGK
Random.seed!(234923);The first step is to define the rank-4 tensor that, when contracted on a square lattice,
evaluates to the partition function value at a given
function classical_ising(; beta=log(1 + sqrt(2)) / 2, J=1.0)
K = beta * J
# Boltzmann weights
t = ComplexF64[exp(K) exp(-K); exp(-K) exp(K)]
r = eigen(t)
nt = r.vectors * sqrt(Diagonal(r.values)) * r.vectors
# local partition function tensor
O = zeros(2, 2, 2, 2)
O[1, 1, 1, 1] = 1
O[2, 2, 2, 2] = 1
@tensor o[-1 -2; -3 -4] := O[3 4; 2 1] * nt[-3; 3] * nt[-4; 4] * nt[-2; 2] * nt[-1; 1]
# magnetization tensor
M = copy(O)
M[2, 2, 2, 2] *= -1
@tensor m[-1 -2; -3 -4] := M[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * nt[-4; 4]
# bond interaction tensor and energy-per-site tensor
e = ComplexF64[-J J; J -J] .* nt
@tensor e_hor[-1 -2; -3 -4] :=
O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * nt[-3; 3] * e[-4; 4]
@tensor e_vert[-1 -2; -3 -4] :=
O[1 2; 3 4] * nt[-1; 1] * nt[-2; 2] * e[-3; 3] * nt[-4; 4]
e = e_hor + e_vert
# fixed tensor map space for all three
TMS = ℂ^2 ⊗ ℂ^2 ← ℂ^2 ⊗ ℂ^2
return TensorMap(o, TMS), TensorMap(m, TMS), TensorMap(e, TMS)
end;So let's initialize these tensors at inverse temperature \beta=0.6, check that
they are indeed rank-4 and construct the corresponding InfinitePartitionFunction:
beta = 0.6
O, M, E = classical_ising(; beta)
@show space(O)
Z = InfinitePartitionFunction(O)InfinitePartitionFunction{TensorKit.TensorMap{ComplexF64, TensorKit.ComplexSpace, 2, 2, Vector{ComplexF64}}}(TensorKit.TensorMap{ComplexF64, TensorKit.ComplexSpace, 2, 2, Vector{ComplexF64}}[TensorMap((ℂ^2 ⊗ ℂ^2) ← (ℂ^2 ⊗ ℂ^2)):
[:, :, 1, 1] =
3.169519816780443 + 0.0im 0.4999999999999995 + 0.0im
0.4999999999999995 + 0.0im 0.1505971059561009 + 0.0im
[:, :, 2, 1] =
0.4999999999999995 + 0.0im 0.1505971059561009 + 0.0im
0.1505971059561009 + 0.0im 0.4999999999999995 + 0.0im
[:, :, 1, 2] =
0.4999999999999995 + 0.0im 0.1505971059561009 + 0.0im
0.1505971059561009 + 0.0im 0.4999999999999995 + 0.0im
[:, :, 2, 2] =
0.1505971059561009 + 0.0im 0.4999999999999995 + 0.0im
0.4999999999999995 + 0.0im 3.169519816780443 + 0.0im
;;])
Next, we can contract the partition function as per usual by constructing a CTMRGEnv with
a specified environment virtual space and calling leading_boundary with appropriate
settings:
Venv = ℂ^20
env₀ = CTMRGEnv(Z, Venv)
env, = leading_boundary(env₀, Z; tol=1e-8, maxiter=500);[ Info: CTMRG init: obj = +1.784252138312e+00 -1.557258880375e+00im err = 1.0000e+00
[ Info: CTMRG conv 63: obj = +3.353928644031e+00 err = 4.5903314811e-09 time = 8.12 sec
Note that CTMRG environments for partition functions differ from the PEPS environments only by the edge tensors. Instead of two legs connecting the edges and the PEPS-PEPS sandwich, there is only one leg connecting the edges and the partition function tensor, meaning that the edge tensors are now rank-3:
space.(env.edges)4×1×1 Array{TensorKit.TensorMapSpace{TensorKit.ComplexSpace, 2, 1}, 3}:
[:, :, 1] =
(ℂ^20 ⊗ ℂ^2) ← ℂ^20
(ℂ^20 ⊗ ℂ^2) ← ℂ^20
(ℂ^20 ⊗ (ℂ^2)') ← ℂ^20
(ℂ^20 ⊗ (ℂ^2)') ← ℂ^20
To compute the value of the partition function, we have to contract Z with the converged
environment using network_value. Additionally, we will compute the magnetization
and energy (per site), again using expectation_value but this time also specifying
the index in the unit cell where we want to insert the local tensor:
λ = network_value(Z, env)
m = expectation_value(Z, (1, 1) => M, env)
e = expectation_value(Z, (1, 1) => E, env)
@show λ m e;λ = 3.3539286440313782 - 5.873212040152551e-16im
m = 0.9736086674403001 + 1.8262157316829647e-17im
e = -1.8637796145082437 - 1.4609725853463717e-16im
In order to assess our results, we will compare against the
exact Onsager solution
of the 2D classical Ising model. To that end, we compute the exact free energy,
magnetization and energy per site (where we use quadgk to perform integrals of an
auxiliary variable from
function classical_ising_exact(; beta=log(1 + sqrt(2)) / 2, J=1.0)
K = beta * J
k = 1 / sinh(2 * K)^2
F = quadgk(
theta -> log(cosh(2 * K)^2 + 1 / k * sqrt(1 + k^2 - 2 * k * cos(2 * theta))), 0, pi
)[1]
f = -1 / beta * (log(2) / 2 + 1 / (2 * pi) * F)
m = 1 - (sinh(2 * K))^(-4) > 0 ? (1 - (sinh(2 * K))^(-4))^(1 / 8) : 0
E = quadgk(theta -> 1 / sqrt(1 - (4 * k) * (1 + k)^(-2) * sin(theta)^2), 0, pi / 2)[1]
e = -J * cosh(2 * K) / sinh(2 * K) * (1 + 2 / pi * (2 * tanh(2 * K)^2 - 1) * E)
return f, m, e
end
f_exact, m_exact, e_exact = classical_ising_exact(; beta);And indeed, we do find agreement between the exact and CTMRG values (keeping in mind that energy accuracy is limited by the environment dimension and the lack of proper extrapolation):
@show (-log(λ) / beta - f_exact) / f_exact
@show (abs(m) - abs(m_exact)) / abs(m_exact)
@show (e - e_exact) / e_exact;(-(log(λ)) / beta - f_exact) / f_exact = -6.605563039765528e-16 - 1.447068124555315e-16im
(abs(m) - abs(m_exact)) / abs(m_exact) = -4.561270094458082e-16
(e - e_exact) / e_exact = -0.023732068099090543 + 7.652732508485748e-17im
This page was generated using Literate.jl.