Skip to content

[BUG] Fail to add noise to TDVP #130

Description

@fliingelephant

Description of bug

When running the time-dependent TDVP example, I tried adding an non-zero noise to the tdvp function and it doesn't work.

Minimal code demonstrating the bug or unexpected behavior

The following is actually a collection of relevant files in the time-dependent TDVP example, with an additional noise parameter in the tdvp function.

Minimal runnable code

using Compat: @compat
using ITensors: ITensor, array, inds, itensor, @disable_warn_order
using ITensorMPS: TimeDependentSum, to_vec, OpSum, MPO, MPS, contract, inner, random_mps, siteinds, tdvp
using LinearAlgebra: norm
using KrylovKit: exponentiate
using OrdinaryDiffEqTsit5
using OrdinaryDiffEqTsit5: ODEProblem, Tsit5, solve 
using Random: Random

function heisenberg(n; J=1.0, J2=0.0)
  ℋ = OpSum()
  if !iszero(J)
    for j in 1:(n - 1)
      ℋ += J / 2, "S+", j, "S-", j + 1+= J / 2, "S-", j, "S+", j + 1+= J, "Sz", j, "Sz", j + 1
    end
  end
  if !iszero(J2)
    for j in 1:(n - 2)
      ℋ += J2 / 2, "S+", j, "S-", j + 2+= J2 / 2, "S-", j, "S+", j + 2+= J2, "Sz", j, "Sz", j + 2
    end
  end
  returnend

function ode_updater(operator, init; internal_kwargs, alg=Tsit5(), kwargs...)
  @compat (; current_time, time_step) = (; current_time=zero(Bool), internal_kwargs...)
  time_span = typeof(time_step).((current_time, current_time + time_step))
  init_vec, to_itensor = to_vec(init)
  f(init::ITensor, p, t) = operator(t)(init)
  f(init_vec::AbstractArray, p, t) = to_vec(f(to_itensor(init_vec), p, t))[1]
  prob = ODEProblem(f, init_vec, time_span)
  sol = solve(prob, alg; kwargs...)
  state_vec = sol.u[end]
  return to_itensor(state_vec), (;)
end

function krylov_updater(operator, init; internal_kwargs, kwargs...)
  @compat (; current_time, time_step) = (; current_time=zero(Bool), internal_kwargs...)
  state, info = exponentiate(operator(current_time), time_step, init; kwargs...)
  return state, (; info)
end

function main(; eltype=Float64, device=identity)
  Random.seed!(1234)

  # Time dependent Hamiltonian is:
  # H(t) = H₁(t) + H₂(t) + …
  #      = f₁(t) H₁(0) + f₂(t) H₂(0) + …
  #      = cos(ω₁t) H₁(0) + cos(ω₂t) H₂(0) + …

  # Number of sites
  n = 6

  # How much information to output from TDVP
  # Set to 2 to get information about each bond/site
  # evolution, and 3 to get information about the
  # updater.
  outputlevel = 3

  # Frequency of time dependent terms
  ω₁ = one(eltype) / 10
  ω₂ = one(eltype) / 5

  # Nearest and next-nearest neighbor
  # Heisenberg couplings.
  J₁ = one(eltype)
  J₂ = one(eltype)

  time_step = one(eltype) / 10
  time_stop = one(eltype)

  # nsite-update TDVP
  nsite = 2

  # Starting state bond/link dimension.
  # A product state starting state can
  # cause issues for TDVP without
  # subspace expansion.
  start_linkdim = 4

  # TDVP truncation parameters
  maxdim = 100
  cutoff = (eps(eltype))

  tol = 10 * eps(eltype)

  @show n
  @show ω₁, ω₂
  @show J₁, J₂
  @show maxdim, cutoff, nsite
  @show start_linkdim
  @show time_step, time_stop

  ω⃗ = (ω₁, ω₂)
  f⃗ = map-> (t -> cos* t)), ω⃗)

  # H₀ = H(0) = H₁(0) + H₂(0) + …
  ℋ₁₀ = heisenberg(n; J=J₁, J2=zero(eltype))
  ℋ₂₀ = heisenberg(n; J=zero(eltype), J2=J₂)
  ℋ⃗₀ = (ℋ₁₀, ℋ₂₀)

  s = siteinds("S=1/2", n)

  H⃗₀ = map(ℋ₀ -> device(MPO(eltype, ℋ₀, s)), ℋ⃗₀)

  # Initial state, ψ₀ = ψ(0)
  # Initialize as complex since that is what OrdinaryDiffEq.jl/DifferentialEquations.jl
  # expects.
  ψ₀ = device(
    complex.(random_mps(eltype, s, j -> isodd(j) ? "" : ""; linkdims=start_linkdim))
  )

  @show norm(ψ₀)

  println()
  println("#"^100)
  println("Running TDVP with ODE updater")
  println("#"^100)
  println()

  ψₜ_ode = tdvp(
    -im * TimeDependentSum(f⃗, H⃗₀),
    time_stop,
    ψ₀;
    updater=ode_updater,
    updater_kwargs=(; reltol=tol, abstol=tol),
    time_step,
    maxdim,
    cutoff,
    nsite,
    outputlevel,
    noise=1e-8,
  )

  println()
  println("Finished running TDVP with ODE updater")
  println()

  println()
  println("#"^100)
  println("Running TDVP with Krylov updater")
  println("#"^100)
  println()

  ψₜ_krylov = tdvp(
    -im * TimeDependentSum(f⃗, H⃗₀),
    time_stop,
    ψ₀;
    updater=krylov_updater,
    updater_kwargs=(; tol, eager=true),
    time_step,
    cutoff,
    nsite,
    outputlevel,
  )

  println()
  println("Finished running TDVP with Krylov updater")
  println()

  println()
  println("#"^100)
  println("Running full state evolution with ODE updater")
  println("#"^100)
  println()

  @disable_warn_order begin
    ψₜ_full, _ = ode_updater(
      -im * TimeDependentSum(f⃗, contract.(H⃗₀)),
      contract(ψ₀);
      internal_kwargs=(; time_step=time_stop, outputlevel),
      reltol=tol,
      abstol=tol,
    )
  end

  println()
  println("Finished full state evolution with ODE updater")
  println()

  @show norm(ψₜ_ode)
  @show norm(ψₜ_krylov)
  @show norm(ψₜ_full)

  @show 1 - abs(inner(contract(ψₜ_ode), ψₜ_full))
  @show 1 - abs(inner(contract(ψₜ_krylov), ψₜ_full))
  return nothing
end

main()

Expected output or behavior

It should work well and give a noisy TDVP result.

Actual output or behavior

Output of minimal runnable code

ERROR: UndefVarError: `phi` not defined in `ITensorMPS`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
  [1] region_update!(nsite_val::Val{…}, reverse_step_val::Val{…}, reduced_operator::TimeDependentSum{…}, state::MPS, b::Int64; updater::typeof(ode_updater), updater_kwargs::@NamedTuple{}, current_time::Float64, time_step::Float64, outputlevel::Int64, normalize::Bool, direction::Base.Order.ForwardOrdering, noise::Float64, which_decomp::Nothing, svd_alg::Nothing, cutoff::Float64, maxdim::Int64, mindim::Int64, maxtruncerr::Float64)
    @ ITensorMPS ~/ITensorMPS.jl/src/solvers/sweep_update.jl:417
  [2] region_update!(reduced_operator::TimeDependentSum{…}, state::MPS, b::Int64; updater::Function, updater_kwargs::@NamedTuple{}, nsite::Int64, reverse_step::Bool, current_time::Float64, outputlevel::Int64, time_step::Float64, normalize::Bool, direction::Base.Order.ForwardOrdering, noise::Float64, which_decomp::Nothing, svd_alg::Nothing, cutoff::Float64, maxdim::Int64, mindim::Int64, maxtruncerr::Float64)
    @ ITensorMPS ~/ITensorMPS.jl/src/solvers/sweep_update.jl:187
  [3] sub_sweep_update(direction::Base.Order.ForwardOrdering, reduced_operator::TimeDependentSum{…}, state::MPS; updater::Function, updater_kwargs::@NamedTuple{}, which_decomp::Nothing, svd_alg::Nothing, sweep::Int64, current_time::Float64, time_step::Float64, nsite::Int64, reverse_step::Bool, normalize::Bool, observer!::ITensorMPS.EmptyObserver, outputlevel::Int64, maxdim::Int64, mindim::Int64, cutoff::Float64, noise::Float64)
    @ ITensorMPS ~/ITensorMPS.jl/src/solvers/sweep_update.jl:107
  [4] sweep_update(order::ITensorMPS.TDVPOrder{…}, reduced_operator::TimeDependentSum{…}, state::MPS; current_time::Float64, time_step::Float64, kwargs::@Kwargs{})
    @ ITensorMPS ~/ITensorMPS.jl/src/solvers/sweep_update.jl:25
  [5] macro expansion
    @ ~/ITensorMPS.jl/src/solvers/alternating_update.jl:69 [inlined]
  [6] macro expansion
    @ ./timing.jl:421 [inlined]
  [7] alternating_update(operator::TimeDependentSum{…}, init::MPS; updater::Function, updater_kwargs::@NamedTuple{}, nsweeps::Int64, checkdone::Returns{…}, write_when_maxdim_exceeds::Nothing, nsite::Int64, reverse_step::Bool, time_start::Float64, time_step::Float64, order::Int64, observer!::ITensorMPS.EmptyObserver, sweep_observer!::ITensorMPS.EmptyObserver, outputlevel::Int64, normalize::Bool, maxdim::Int64, mindim::Int64, cutoff::Float64, noise::Float64)
    @ ITensorMPS ~/ITensorMPS.jl/src/solvers/alternating_update.jl:68
  [8] alternating_update
    @ ~/ITensorMPS.jl/src/solvers/alternating_update.jl:23 [inlined]
  [9] #tdvp#767
    @ ~/ITensorMPS.jl/src/solvers/tdvp.jl:86 [inlined]
 [10] main(; eltype::Type, device::typeof(identity))
    @ Main ~/ITensorMPS.jl/examples/solvers/03_tdvp_time_dependent.jl:105
 [11] main()
    @ Main ~/ITensorMPS.jl/examples/solvers/03_tdvp_time_dependent.jl:27
 [12] top-level scope
    @ ~/ITensorMPS.jl/examples/solvers/03_tdvp_time_dependent.jl:174
Some type information was truncated. Use `show(err)` to see complete types.

Version information

  • Output from versioninfo():
julia> versioninfo()
Julia Version 1.11.3
Commit d63adeda50d (2025-01-21 19:42 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 128 × Intel(R) Xeon(R) Platinum 8378A CPU @ 3.00GHz
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, icelake-server)
Threads: 1 default, 0 interactive, 1 GC (on 128 virtual cores)
Environment:
  JULIA_EDITOR = code
  JULIA_NUM_THREADS =
  • Output from using Pkg; Pkg.status("ITensorMPS"):
julia> using Pkg; Pkg.status("ITensorMPS")
Project ITensorMPS v0.3.14
No Matches in `~/ITensorMPS.jl/Project.toml`

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions