Hello,
I have made some numerical experiments (“Code1” and “Code2” below) using the ITensorQTT.jl
package and I am getting some unexpected error scaling with respect to the system size n. I test the error by transforming a discrete function \vec f, i.e., a vector of size N=2^n, into an MPS representation \psi_f followed by the inverse transformation (see “Code1” below):
My expectation is to see ||\vec f-\vec{f'}||_2 \approx \text{const.} \approx 10^{-16} (depending on machine precision) for all values of n, since the MPS construction is not truncated by cutoff
nor maxdim
arguments. This can be verified by the fact that maxlinkdim(ψ_f) = 2^(n/2)
, the maximum possible.
The MPS construction should be exact. However, I observe an exponential dependence
This problem also appears, when one tries to reproduce (see “Code2” below) the truncation error bound by Verstraete and Cirac, here in the context of QTT, with the ITensorQTT.jl
package. Where one expects
\psi_f is the exact MPS construction and {\psi_f}_D is a truncated MPS with maxdim = D
. Alternatively, one may use cutoff
to truncate.
I have the following questions, I hope someone may be able to help:
- Is machine precision to blame?
- Are the MPS construction and the inverse not exact?
- Am I missing something else?
Thank you in advance. Below you’ll find some code abstracts from the ITensorQTT.jl
package along with minimal examples “Code1”, and “Code2”.
Source code abstract from ITensor/ITensorQTT.jl/src/function_to_mps.jl
, lines 132-177:
#
# MPS to function conversion
#
function mps_to_discrete_function(ψ::MPS; kwargs...)
return mps_to_discrete_function(Val(1), ψ; kwargs...)
end
function mps_to_discrete_function(::Val{ndims}, ψ::MPS) where {ndims}
n = length(ψ)
s = siteinds(ψ)
s⃗ = ntuple(j -> s[j:ndims:n], Val(ndims))
return reshape(Array(contract(ψ), vcat(reverse.(s⃗)...)), dim.(s⃗))
end
function mps_to_discrete_function(ψ::MPS, nbits::Integer; kwargs...)
return mps_to_discrete_function(Val(1), ψ, nbits; kwargs...)
end
function mps_to_discrete_function(ψ::MPS, nbits::Tuple{Vararg{Integer}}; kwargs...)
@assert allequal(nbits)
ndims = length(nbits)
nbits_original = ntuple(Returns(length(ψ) ÷ ndims), Val(ndims))
nbits_retract = nbits_original .- nbits
ψ = retract(ψ, nbits_retract; kwargs...)
return mps_to_discrete_function(Val(ndims), ψ)
end
function mps_to_discrete_function(
::Val{ndims}, ψ::MPS, nbits::Integer; kwargs...
) where {ndims}
return mps_to_discrete_function(ψ, ntuple(Returns(nbits), Val(ndims)))
end
#
# Vector/MPS conversion
#
function vec_to_tensor(v)
n = Int(log2(length(v)))
return permutedims(reshape(v, fill(2, n)...), n:-1:1)
end
function vec_to_mps(v, s; kwargs...)
return MPS(vec_to_tensor(v), s; kwargs...)
end
Code1:
using ITensors
using ITensorsQTT
ITensors.disable_warn_order()
l2_err = []
ns = [2, 5, 10, 15, 20]
xstart = -20
xstop = 20
for n in ns
x = qtt_xrange(n, xstart, xstop)
s = siteinds("Qubit", n)
# discrete function (vector)
f_vec = sin.(x)
# vector to MPS
ψ_f = vec_to_mps(f_vec, s)
# MPS back to vector
ψ_vec = mps_to_discrete_function(ψ_f)
# calculate error
push!(l2_err, norm(f_vec - ψ_vec))
end
Code2:
ITensors.disable_warn_order()
l2_err = []
ns = [2, 5, 10, 15, 20]
xstart = -20
xstop = 20
for n in ns
x = qtt_xrange(n, xstart, xstop)
s = siteinds("Qubit", n)
# discrete function
f_vec = sin.(x)
# function to MPS
ψ_f = vec_to_mps(f_vec, s)
truncate!(ψ_f, maxdim = 2^(n/2)) # or mindim = 2^(n/2)
# truncate MPS
ψ_tr = truncate(ψ_f, cutoff = 1e-16) # or maxdim = 2
# calculate error
push!(l2_err, norm(ψ_f - ψ_tr))
end
Now ploting l2_err
with the y-axis in logarithmic scale shows an exponential scaling in n.
plot(ns, max.(l2_err, 1e-16), yscale = :log10)