ITensorNetworks inner function with alg="bp" issue

Hi,

I have been trying to use inner(psi, ψ, alg="bp") where psi is an initial state and ψ a time-evolved state but for some choices of psi and evolutions of ψ this value blows up even with normalization and doesn’t produce the expected result. What could be causing this problem? For example when the initial state psi is defined by starting with a z-polarized state of a two heavy hexagonal system of size L=21 and applying X operators at 1’s from the bitstring [1 0 0 1 0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 1], and the state ψ is evolved from a z-polarized state through a kicked Ising unitary, then inner(psi, ψ, alg="bp") blows up only at the 10th evolution step but not the 9th or 11th. This is the case for more initial states at later times and for a larger system size.

Julia version 1.10.4
ITensors version 0.6.23
ITensorNetworks version 0.11.21

Hi @jsaroni thanks for the question. A lot of ITensorNetworks has not gone through rigorous testing yet so this is very helpful for us to identify issues.

Can you specify what do you mean by blow up? Does it return nan ? Perhaps you could share a minimal working example of your code so I could try to diagnose it?

1 Like

Hi @JoeyT1994 thank you for your response! The functions scalar, loginner, and logscaler from ITensorNetworks.jl/test/test_inner.jl at main · ITensor/ITensorNetworks.jl · GitHub have the same behavior. The code doesn’t return nan but I mean a sudden large increase in the probability amplitude value for a bitstring as a state is evolved. The code should return a probability amplitude << 1 for the particular computational bitstring used but returns a finite value of 21.6200042978372. When \chi is increased it does give an expected result so I’m not sure why it is so different for lower \chi. Also tried normalize=True in the apply function but this gives amplitudes much smaller than expected.

using ITensors
using ITensorNetworks
using NamedGraphs
using NamedGraphs: rem_edge!, add_edge!
using Graphs
using ITensorUnicodePlots
using ITensorNetworks: expect, apply
using Graphs: vertices
using ITensorNetworks:
  BeliefPropagationCache,
  ITensorNetwork,
  VidalITensorNetwork,
  environment,
  norm_sqr_network,
  siteinds,
  update,
  scalar,
  inner_network,
  loginner,
  logscalar
using ITensors: ITensors, inner, op
using NamedGraphs.NamedGraphGenerators: named_grid
using NamedGraphs.PartitionedGraphs: PartitionVertex
using SplitApplyCombine: group
using ITensorNetworks.ModelHamiltonians: ising

# Sampled computational bitstrings. One in this case. 
s_dec = [2095441]

# Convert bitstring decimal to binary
function decimal_to_binary(decimal_number::Int)
    binary_digits = String[]
    
    while decimal_number > 0
        remainder = decimal_number % 2
        pushfirst!(binary_digits, string(remainder))
        decimal_number ÷= 2
    end
    
    return isempty(binary_digits) ? "0" : join(binary_digits)
end

# Evaluate probabilities corresponding to the sampled computational bitstrings
function main(θh::Float64, no_trotter_steps::Int64; apply_kwargs...)
  
  # Build a system
  L = 21
  g_dims = (1, 1)
  n = prod(g_dims)
  g = named_grid(g_dims)
  for i in 1:21
    add_vertex!(g, (i,i))
  end 
  for i in 1:8
    add_edge!(g, (i,i), (i+1,i+1))
  end  
  for i in 13:20
    add_edge!(g, (i,i), (i+1,i+1))
  end  
  add_edge!(g, (1,1), (10,10))
  add_edge!(g, (10,10), (13,13))
  add_edge!(g, (5,5), (11,11))
  add_edge!(g, (11,11), (17,17))  
  add_edge!(g, (9,9), (12,12))
  add_edge!(g, (12,12), (21,21))

  s = siteinds("S=1/2", g)
  ψ = ITensorNetwork(v -> "↑", s)
  @visualize ψ
  #z-polarized initial state
  psi_init = ψ
  
  X = OpSum()
  for i=1:L
    add!(X, 1.0, "X", (i,i))
  end
  X = Vector{ITensor}(X, s)
  X = vcat(X)

  v1, v2 = (1, 1), (2, 2)
  ψψ = norm_sqr_network(ψ)
  #Simple Belief Propagation Grouping
  bp_cache = BeliefPropagationCache(ψψ, group(v -> v[1], vertices(ψψ)))
  bp_cache = update(bp_cache; maxiter=20)
  envsSBP = environment(bp_cache, PartitionVertex.([v1, v2]))
 
  #Build gates
  HX, HZZ = ising(g; h=θh / 2, J1=0), ising(g; h=0, J1=-pi / 4)
  RX, RZZ = exp(-im * 2 * HX; alg=Trotter{1}()), exp(-im* 4 * HZZ; alg=Trotter{1}())
  RX_gates, RZZ_gates = Vector{ITensor}(RX, s), Vector{ITensor}(RZZ, s)

  gates = vcat(RX_gates, RZZ_gates)

  for i in 1:no_trotter_steps
    for gate in gates
      ψ = apply(gate, ψ; envs=envsSBP, nfullupdatesweeps=10, maxdim=χ, cutoff=1e-16)
    end
  end  

  # Apply X only at initial state sites where binary digits from the sampled bitstrings are 1. 
  pztbp = []
  for e in s_dec
    t1 = time()
    psi = psi_init
    b = decimal_to_binary(e-1)
    b = lpad(b, L, "0")
    for i in 1:L
      if parse(Int, b[i]) == 1
        psi = apply(X[i], psi, cutoff=1e-16)
      end 
    end
    
    # Compute probability amplitude associated with bitstring
    push!(pztbp, real(inner(psi, ψ, alg="bp")conj(inner(psi, ψ, alg="bp")))/(real(inner(ψ, ψ, alg="bp"))))

  end
  println(pztbp)
  return pztbp
end

θh = pi / 4
no_trotter_steps = 10
χ = 64

mags = main(θh, no_trotter_steps)

Thanks for sharing the code.

There are a few things going on here that point to some potential issues.

You are computing BP environments for a specific pair of vertices via the line envsSBP = environment(bp_cache, PartitionVertex.([v1, v2])) at the start of the evolution and then never updating them during the simulation. This means the truncations during your time evolution will be off and take the state in a different direction than you expect. Hence when you use a smaller bond dimension than you need (i.e. you make truncations) you get an answer that doesn’t make sense.

To get a reasonable answer, you will need to dynamically update the BeliefPropagationCache and extract environments from it during the simulation.

The following high-level function will help you to do that:

function ITensors.apply(
  o::ITensor,
  ψ::AbstractITensorNetwork,
  bpc::BeliefPropagationCache;
  reset_all_messages=false,
  apply_kwargs...,
)
  bpc = copy(bpc)
  ψ = copy(ψ)
  vs = neighbor_vertices(ψ, o)
  envs = environment(bpc, PartitionVertex.(vs))
  singular_values! = Ref(ITensor())
  ψ = noprime(apply(o, ψ; envs, singular_values!, apply_kwargs...))
  ψdag = prime(dag(ψ))
  if length(vs) == 2
    v1, v2 = vs
    pe = partitionedge(bpc, (v1, "bra") => (v2, "bra"))
    mts = messages(bpc)
    ind2 = commonind(singular_values![], ψ[v1])
    δuv = dag(copy(singular_values![]))
    δuv = replaceind(δuv, ind2, ind2')
    map_diag!(sign, δuv, δuv)
    singular_values![] = denseblocks(singular_values![]) * denseblocks(δuv)
    if !reset_all_messages
      set!(mts, pe, dag.(ITensor[singular_values![]]))
      set!(mts, reverse(pe), ITensor[singular_values![]])
    else
      bpc = BeliefPropagationCache(partitioned_tensornetwork(bpc))
    end
  end
  for v in vs
    bpc = update_factor(bpc, (v, "ket"), ψ[v])
    bpc = update_factor(bpc, (v, "bra"), ψdag[v])
  end
  return ψ, bpc
end

allowing you to replace your time-evolution loop with the following

 for i in 1:no_trotter_steps
    for gate in gates
      ψ, bp_cache = apply(gate, ψ, bp_cache; maxdim=χ, cutoff=1e-16)
    end
    bp_cache = update(bp_cache; maxiter = 20)
  end  

which will dynamically extract the correct environments during the call to apply and sporadically update the environments after every Trotter step.

Note that this is all still approximate under the BP approximation and so caution should be taken with the results you get.
If you provide me with your GitHub user I can give you access to an example repository with scripts for doing TEBD etc.

@JoeyT1994 I see. Thanks. When the bp cache is updated the actual values of the amplitudes do look reasonable which solves the issue I had but still need to test the actual values.

My GitHub username is jsaroni

Sharing this comparison. This error occurs at T=20 when updating the cache and using the apply function: “ERROR: LoadError: ArgumentError: Trying to perform the eigendecomposition of a matrix containing NaNs or Infs”

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.