State evolution in Bose-Hubbard model - Julia

Hey ITensor team and users,

I am currently working on simulating the dynamics of the Bose-Hubbard model with nearest-neighbor interactions using ITensor in Julia. I have successfully implemented the time-independent version, but I am facing challenges in adapting it for the time-dependent case. I followed the tutorial provided on the ITensor website (MPSTimeEvolution) and incorporated other relevant snippets from discussions in this group.

As a test case, I am trying to evolve a density wave initial state (hardcore bosons) towards a superfluid state by applying only the hopping term, -t * (aᵢ† aⱼ + aⱼ† aᵢ), to this initial state. However, the energy values I obtain suggest that my code isn’t behaving as expected.

Since I am relatively new to Julia and the dynamics part, I would greatly appreciate any guidance or hints. I have attached my code below for reference.

Additionally, I have a couple of specific questions:

  1. Does the term c = div(N,2) from the template handle systems with an odd number of sites properly?
  2. Between TEBD and TDVP, which method would be more suitable for my case? I am currently using TEBD but am unsure if switching to TDVP would improve results.

Thank you in advance for any help!

Best regards,
Aman

Code :

using ITensors, ITensorMPS

function ITensors.space(::SiteType"S=0";
                        conserve_qns=false)
  if conserve_qns
    return [QN("nb",0)=>1,QN("nb",1)=>1]
  end
  return 5
end

function ITensors.op!(Op::ITensor,
                      ::OpName"nb",
                      ::SiteType"S=0",
                      s::Index)
  Op[s'=>2,s=>2] = 1
end

function ITensors.op!(Op::ITensor,
                      ::OpName"aop",
                      ::SiteType"S=0",
                      s::Index)
  Op[s'=>1,s=>2] = sqrt(1)
end

function ITensors.op!(Op::ITensor,
                      ::OpName"adop",
                      ::SiteType"S=0",
                      s::Index)
  Op[s'=>2,s=>1] = sqrt(1)
end

s = siteind("S=0"; conserve_qns=true)
nb = op("nb",s)
adop = op("adop",s)
aop = op("aop",s)

let
  N      = 11
  Nb     = ((N-1)/2)+1
  maxocc = 1
  mltplt = maxocc+1
  tb1    = 1.0
  tb2    = 0.0
  Vb     = 0.0

  sites = siteinds("S=0",N; conserve_qns=true)

  ampo = AutoMPO()
  for b=1:N-1
    ampo += -tb1,"adop",b,"aop",b+1
    ampo += -tb1,"adop",b+1,"aop",b
  end

  for b=1:N-2
    if b % 2 == 1
      ampo += -tb2,"adop",b,"aop",b+2
      ampo += -tb2,"adop",b+2,"aop",b
    end
  end

  for i=1:N-1
    ampo += +Vb,"nb",i,"nb",i+1
  end

  H = MPO(ampo,sites)

  p = Nb
  state = [1 for n=1:N]
  for i=N:-1:1
   if p >=0
    if isodd(i)
       state[i] = 2
       p -= 1
    else
       state[i] = 1
    end
   end
  end

  psi0 = productMPS(sites,state)
  @show flux(psi0)
  psi = psi0
  energy = 0.0
  println("\nGround State Energy = $energy")
  nlocal = expect(psi,"nb")
  @show nlocal
  nncorr = correlation_matrix(psi,"nb","nb")
  println(nlocal)

  tau = 0.05
  ttotal = 20.0
  cutoff = 1E-8
  # Make gates (1,2),(2,3),(3,4),...
  gates = ITensor[]
  for j in 1:(N - 1)
    n1 = sites[j]
    n2 = sites[j + 1]
    hj = -tb1 * (op("adop", n1) * op("aop", n2) + op("aop", n1) * op("adop", n2))
    Gj = exp(-1.0im * tau / 2 * hj)
    push!(gates, Gj)
  end
  # Include gates in reverse order too
  # (N,N-1),(N-1,N-2),...
  append!(gates, reverse(gates))

  c = div(N, 2) # center site

  # Compute and print energy at each time step
  # then apply the gates to go to the next time
  for t in 0.0:tau:ttotal
    energy_dyn = inner(psi', H, psi)
    println("$t $energy_dyn")
    energy_squared = inner(H, psi, H, psi)
    variance = energy_squared - energy^2
    println("\nEnergy variance = $variance")

    t≈ttotal && break

    psi = apply(gates, psi; cutoff)
    normalize!(psi)
  end
  nlocal = expect(psi,"nb")
  @show nlocal

  return
end

Hi Aman,
The code c = div(N,2) is the Julia integer division function. For even values of N, it returns N/2. For odd values of N, it returns (N-1)/2. I will let you decide if that behavior is what you need or not.

Note that there is no requirement that you use div(N,2) or even the variable c as we defined it. It was just shown there as an example of measuring a local property at a certain site near the center. In your modified code, it looks like you are not even using c, so you can just delete that line.

About TEBD versus TDVP, I would say the main reasons to use TDVP are when you are doing a system with complicated or long-range interaction such that you need an MPO, or you are doing a very expensive time evolution, where TDVP can offer the benefit of being more efficient. But if you can use TEBD, especially if you are doing a 1D system with short-range interactions, then I would say TEBD is the best “safe” choice because it automatically adapts the bond dimension very nicely after each step whereas with TDVP this can currently be a much trickier issue, and TDVP can sometimes give wrong answers unless you carefully check it and perform certain “subspace expansion” steps to help it to converge, which we currently only have partial support for.

So in short, I would recommend TEBD for your case, which I think is a 1D short-range case.

Finally, I am not sure why you are not getting the results you expect but please do carefully consider the role of finite-size and open-boundary effects, and also please explore various parameter settings like the maxdim and cutoff parameters controlling the truncation at each time step.

Hi Dr. Miles,
Could I ask a simple question about ITensor.jl for boson system under this post? Recently I switched my ITensor from C++ version to Julia version. In C++ version I defined the SiteType as sites = Boson(L, {"MaxOcc=", n, "ConserveQNs", true, "ConserveNb", false}) to make a cutoff on the maximum occupation to reduce the dimension. In Julia version I haven’t found such a setting for the cutoff from the guidance. I am wondering if there is a similar embedded commands for the maximum occupancy setting in Julia version? Or how could I set such a cutoff? Think you so much for your nice work.

Hi Ning,
Would you please post your question as a new topic / new post? I think it is a good question, but deserves its own separate post. Thanks.

Miles