diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index aff088f..820641b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,6 +23,7 @@ jobs: fail-fast: false matrix: version: + - '1.9' - 'lts' - '1' os: diff --git a/Project.toml b/Project.toml index 76f3d20..0e984b1 100644 --- a/Project.toml +++ b/Project.toml @@ -10,12 +10,21 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +[weakdeps] +ITensorMPS = "0d1a4710-d33b-49a5-8f18-73bdf49b47e2" +ITensors = "9136182c-28ba-11e9-034c-db9fb085ebd5" + +[extensions] +TCIITensorConversion = ["ITensors", "ITensorMPS"] + [compat] BitIntegers = "0.3.5" EllipsisNotation = "1" +ITensorMPS = "0.2, 0.3" +ITensors = "0.6, 0.7, 0.8, 0.9" QuadGK = "2.9" -Random = "1.10.0" -julia = "1.6" +Random = "1.9.0" +julia = "1.9" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" @@ -28,4 +37,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [targets] -test = ["Aqua", "Test", "Random", "ITensors", "Zygote", "Optim", "QuanticsGrids", "JET"] +test = ["Aqua", "Test", "Random", "ITensors", "ITensorMPS", "Zygote", "Optim", "QuanticsGrids", "JET"] diff --git a/docs/Project.toml b/docs/Project.toml index 6270074..8cbf9b4 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,7 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +ITensorMPS = "0d1a4710-d33b-49a5-8f18-73bdf49b47e2" +ITensors = "9136182c-28ba-11e9-034c-db9fb085ebd5" LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" TensorCrossInterpolation = "b261b2ec-6378-4871-b32e-9173bb050604" diff --git a/docs/make.jl b/docs/make.jl index 5bfce32..b63d640 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,10 +1,21 @@ using TensorCrossInterpolation +using ITensors +using ITensorMPS using Documenter -DocMeta.setdocmeta!(TensorCrossInterpolation, :DocTestSetup, :(using TensorCrossInterpolation); recursive=true) +preamble = quote + using TensorCrossInterpolation + using ITensors + using ITensorMPS +end + +DocMeta.setdocmeta!(TensorCrossInterpolation, :DocTestSetup, preamble; recursive=true) makedocs(; - modules=[TensorCrossInterpolation], + modules=[ + TensorCrossInterpolation, + Base.get_extension(TensorCrossInterpolation, :TCIITensorConversion) + ], authors="Ritter.Marc and contributors", sitename="TensorCrossInterpolation.jl", format=Documenter.HTML(; @@ -14,7 +25,7 @@ makedocs(; pages=[ "Home" => "index.md", "Documentation" => "documentation.md", - "Implementation details" => "implementation.md" + "Extensions" => "extensions.md", ] ) diff --git a/docs/src/extensions.md b/docs/src/extensions.md new file mode 100644 index 0000000..9d98f89 --- /dev/null +++ b/docs/src/extensions.md @@ -0,0 +1,17 @@ +# Extensions + +This page documents extensions to [TensorCrossInterpolation](https://github.com/tensor4all/TensorCrossInterpolation.jl). + +Currently, there is only one extension, TCIITensorConversion. + +## TCIITensorConversion + +Dependencies: +- [ITensors.jl](https://github.com/ITensor/ITensors.jl) +- [ITensorMPS.jl](https://github.com/ITensor/ITensorMPS.jl) + +This module is automatically loaded when ITensors and ITensorMPS are present in addition to TensorCrossInterpolation. It offers conversion constructors between ITensorMPS types and TensorCrossInterpolation types, and an analogue of the [`evaluate`](@ref) function for ITensor types. + +```@autodocs +Modules = [Base.get_extension(TensorCrossInterpolation, :TCIITensorConversion)] +``` diff --git a/ext_disabled/TCIITensorConversion.jl b/ext/TCIITensorConversion/TCIITensorConversion.jl similarity index 53% rename from ext_disabled/TCIITensorConversion.jl rename to ext/TCIITensorConversion/TCIITensorConversion.jl index e293456..e08c219 100644 --- a/ext_disabled/TCIITensorConversion.jl +++ b/ext/TCIITensorConversion/TCIITensorConversion.jl @@ -1,10 +1,14 @@ module TCIITensorConversion import TensorCrossInterpolation as TCI +import TensorCrossInterpolation: evaluate + using ITensors +import ITensorMPS +import ITensorMPS: MPS, MPO -export MPS -export evaluate_mps +export MPS, MPO +export evaluate include("ttmpsconversion.jl") include("mpsutil.jl") diff --git a/ext/TCIITensorConversion/mpsutil.jl b/ext/TCIITensorConversion/mpsutil.jl new file mode 100644 index 0000000..7bf8c5c --- /dev/null +++ b/ext/TCIITensorConversion/mpsutil.jl @@ -0,0 +1,50 @@ +@doc raw""" + function evaluate( + mps::Union{ITensorMPS.MPS,ITensorMPS.MPO}, + indexspecs::Vararg{AbstractVector{<:Tuple{ITensorMPS.Index,Int}}} + ) + +Evaluates an MPS or MPO for a given set of index values. + +- `indexspec` is a list of tuples, where each tuple contains an `ITensorMPS.Index` object specifying an index, and an `Int` corresponding to the value of the specified index. + +If many evaluations are necessary, it may be advantageous to convert your MPS to a [`TensorCrossInterpolation.TTCache`](@ref) object first. +""" +function evaluate( + mps::Union{ITensorMPS.MPS,ITensorMPS.MPO}, + indexspecs::Vararg{AbstractVector{<:Tuple{ITensors.Index,Int}}} +) + if isempty(indexspecs) + error("Please specify at which indices you wish to evaluate the MPS.") + elseif any(length.(indexspecs) .!= length(mps)) + error("Need one index per MPS leg") + end + + V = ITensor(1.0) + for j in eachindex(indexspecs[1]) + states = prod(ITensorMPS.state(spec[j]...) for spec in indexspecs) + V *= mps[j] * states + end + return scalar(V) +end +@doc raw""" + function evaluate( + mps::Union{ITensorMPS.MPS,ITensorMPS.MPO}, + indices::AbstractVector{<:ITensors.Index}, + indexvalues::AbstractVector{Int} + ) + +Evaluates an MPS or MPO for a given set of index values. + +- `indices` is a list of `ITensors.Index` objects that specifies the order in which indices are given. +- `indexvalues` is a list of integer values in the same order as `indices`. + +If many evaluations are necessary, it may be advantageous to convert your MPS to a [`TensorCrossInterpolation.TTCache`](@ref) object first. +""" +function evaluate( + mps::Union{ITensorMPS.MPS,ITensorMPS.MPO}, + indices::AbstractVector{<:ITensors.Index}, + indexvalues::AbstractVector{Int} +) + return evaluate(mps, collect(zip(indices, indexvalues))) +end diff --git a/ext_disabled/ttmpsconversion.jl b/ext/TCIITensorConversion/ttmpsconversion.jl similarity index 56% rename from ext_disabled/ttmpsconversion.jl rename to ext/TCIITensorConversion/ttmpsconversion.jl index 7b39175..11d2f0d 100644 --- a/ext_disabled/ttmpsconversion.jl +++ b/ext/TCIITensorConversion/ttmpsconversion.jl @@ -1,15 +1,4 @@ - -""" - MPS(tt, siteindices...) - -Convert a tensor train to an ITensor MPS - - * `tt` Tensor train - * `siteindices` Arrays of ITensor Index objects. - - If `siteindices` is left empty, a default set of indices will be used. -""" -function ITensors.MPS(tt::TCI.TensorTrain{T}; sites=nothing)::MPS where {T} +function ITensorMPS.MPS(tt::TCI.TensorTrain{T}; sites=nothing)::MPS where {T} N = length(tt) localdims = [size(t, 2) for t in tt] @@ -30,11 +19,21 @@ function ITensors.MPS(tt::TCI.TensorTrain{T}; sites=nothing)::MPS where {T} return MPS(tensors_) end -function ITensors.MPS(tci::TCI.AbstractTensorTrain{T}; sites=nothing)::MPS where {T} - return MPS(TCI.tensortrain(tci), sites=sites) +""" + MPS(tt::TCI.AbstractTensorTrain{T}; sites=nothing) + +Convert a tensor train to an `ITensorMPS.MPS`. +Arguments: +- `tt`: The tensor train to be converted. +- `siteindices`: An array of ITensor Index objects, where `sites[n]` corresponds to the index for the nth site. + +If `siteindices` is left empty, a default set of indices will be generated. +""" +function ITensorMPS.MPS(tt::TCI.AbstractTensorTrain{T}; sites=nothing)::MPS where {T} + return MPS(TCI.tensortrain(tt), sites=sites) end -function ITensors.MPO(tt::TCI.TensorTrain{T}; sites=nothing)::MPO where {T} +function ITensorMPS.MPO(tt::TCI.TensorTrain{T}; sites=nothing)::ITensorMPS.MPO where {T} N = length(tt) localdims = TCI.sitedims(tt) @@ -57,22 +56,32 @@ function ITensors.MPO(tt::TCI.TensorTrain{T}; sites=nothing)::MPO where {T} tensors_[1] *= onehot(links[1] => 1) tensors_[end] *= onehot(links[end] => 1) - return MPO(tensors_) + return ITensorMPS.MPO(tensors_) end -function ITensors.MPO(tci::TCI.AbstractTensorTrain{T}; sites=nothing)::MPO where {T} - return MPO(TCI.tensortrain(tci), sites=sites) +""" + MPO(tt::TCI.AbstractTensorTrain{T}; sites=nothing) + +Convert a tensor train to an `ITensorMPS.MPO`. +Arguments: +- `tt`: The tensor train to be converted. +- `sites`: An array of arrays of ITensor Index objects, where `sites[n][m]` corresponds to the mth index attached to the nth site. + +If `siteindices` is left empty, a default set of indices will be generated. +""" +function ITensorMPS.MPO(tci::TCI.AbstractTensorTrain{T}; sites=nothing)::ITensorMPS.MPO where {T} + return ITensorMPS.MPO(TCI.tensortrain(tci), sites=sites) end """ - function TCI.TensorTrain(mps::ITensors.MPS) + function TensorTrain(mps::ITensorMPS.MPS) -Converts an ITensor MPS object into a TensorTrain. Note that this only works if the MPS has a single leg per site! Otherwise, use [`TCI.TensorTrain(mps::ITensors.MPO)`](@ref). +Converts an `ITensorMPS.MPS` object into a TensorTrain. Note that this only works if the MPS has a single leg per site. Otherwise, use [`TensorCrossInterpolation.TensorTrain(mps::ITensorMPS.MPO)`](@ref). """ -function TCI.TensorTrain(mps::ITensors.MPS) - links = linkinds(mps) - sites = siteinds(mps) +function TCI.TensorTrain(mps::ITensorMPS.MPS) + links = ITensorMPS.linkinds(mps) + sites = ITensors.SiteTypes.siteinds(mps) Tfirst = zeros(ComplexF64, 1, dim(sites[1]), dim(links[1])) Tfirst[1, :, :] = Array(mps[1], sites[1], links[1]) Tlast = zeros(ComplexF64, dim(links[end]), dim(sites[end]), 1) @@ -87,15 +96,15 @@ function TCI.TensorTrain(mps::ITensors.MPS) end """ - function TCI.TensorTrain(mps::ITensors.MPO) + function TensorTrain(mps::ITensorMPS.MPO) -Convertes an ITensor MPO object into a TensorTrain. +Converts an `ITensorMPS.MPO` object into a [`TensorCrossInterpolation.TensorTrain`](@ref). """ -function TCI.TensorTrain{V, N}(mpo::ITensors.MPO; sites=nothing) where {N, V} - links = linkinds(mpo) +function TCI.TensorTrain{V, N}(mpo::ITensorMPS.MPO; sites=nothing) where {N, V} + links = ITensorMPS.linkinds(mpo) if sites === nothing - sites = siteinds(mpo) - elseif !all(issetequal.(siteinds(mpo), sites)) + sites = ITensors.SiteTypes.siteinds(mpo) + elseif !all(issetequal.(ITensors.SiteTypes.siteinds(mpo), sites)) error("Site indices do not correspond to the site indices of the MPO.") end diff --git a/ext_disabled/mpsutil.jl b/ext_disabled/mpsutil.jl deleted file mode 100644 index 7e2cf91..0000000 --- a/ext_disabled/mpsutil.jl +++ /dev/null @@ -1,25 +0,0 @@ -function TCI.evaluate( - mps::Union{ITensors.MPS,ITensors.MPO}, - indexspecs::Vararg{AbstractVector{<:Tuple{ITensors.Index,Int}}} -) - if isempty(indexspecs) - error("Please specify at which indices you wish to evaluate the MPS.") - elseif any(length.(indexspecs) .!= length(mps)) - error("Need one index per MPS leg") - end - - V = ITensor(1.0) - for j in eachindex(indexspecs[1]) - states = prod(state(spec[j]...) for spec in indexspecs) - V *= mps[j] * states - end - return scalar(V) -end - -function TCI.evaluate( - mps::Union{ITensors.MPS,ITensors.MPO}, - indices::AbstractVector{<:ITensors.Index}, - indexvalues::AbstractVector{Int} -) - return TCI.evaluate(mps, collect(zip(indices, indexvalues))) -end diff --git a/test/runtests.jl b/test/runtests.jl index 768cb06..0cf3ca8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,3 +21,6 @@ include("test_conversion.jl") include("test_contraction.jl") include("test_integration.jl") include("test_globalsearch.jl") + +# TCIITensorConversion extension +include("test_TCIITensorConversion.jl") diff --git a/test/test_TCIITensorConversion.jl b/test/test_TCIITensorConversion.jl new file mode 100644 index 0000000..e3ed726 --- /dev/null +++ b/test/test_TCIITensorConversion.jl @@ -0,0 +1,100 @@ +using Test + +import TensorCrossInterpolation as TCI +import TensorCrossInterpolation: evaluate +using ITensors +using ITensorMPS + +@testset "TCIITensorConversion.jl" begin + @testset "TT to MPS conversion" begin + tt = TCI.TensorTrain([rand(1, 4, 4), rand(4, 4, 2), rand(2, 4, 7), rand(7, 4, 1)]) + mps = ITensorMPS.MPS(tt) + @test ITensorMPS.linkdims(mps) == [4, 2, 7] + end + + @testset "TT to MPO conversion and back" begin + tt = TCI.TensorTrain([rand(1, 4, 3, 4), rand(4, 2, 4, 2), rand(2, 5, 1, 7), rand(7, 9, 4, 1)]) + mpo = ITensorMPS.MPO(tt) + @test ITensorMPS.linkdims(mpo) == [4, 2, 7] + @test dim.(siteinds(mpo)[1]) == [4, 3] + @test dim.(siteinds(mpo)[2]) == [2, 4] + @test dim.(siteinds(mpo)[3]) == [5, 1] + @test dim.(siteinds(mpo)[4]) == [9, 4] + + tt2 = TCI.TensorTrain{Float64,4}(mpo) + @test all(tt2[i] == tt[i] for i in 1:length(tt)) + + sites = reverse.(siteinds(mpo)) + ttreverse = TCI.TensorTrain{Float64,4}(mpo, sites=sites) + @test all(permutedims(ttreverse[i], [1, 3, 2, 4]) == tt[i] for i in 1:length(tt)) + end + + function binary_representation(index::Int, numdigits::Int) + return Int[(index & (1 << (numdigits - i))) != 0 for i in 1:numdigits] + end + + @testset "mps util" begin + @testset "one mps evaluation" begin + m = 5 + mps = MPS(m) + linkindices = [Index(2, "link") for i in 1:(m-1)] + mps[1] = ITensor( + ones(2, 2), + Index(2, "site"), + linkindices[1] + ) + for i in 2:(m-1) + mps[i] = ITensor( + ones(2, 2, 2), + linkindices[i-1], + Index(2, "site"), + linkindices[i] + ) + end + mps[m] = ITensor( + ones(2, 2), + linkindices[m-1], + Index(2, "site") + ) + + for i in 1:(2^m) + q = binary_representation(i, m) .+ 1 + @test evaluate(mps, siteinds(mps), q) == 2^(m - 1) + @test evaluate(mps, collect(zip(siteinds(mps), q))) == 2^(m - 1) + end + end + + @testset "delta mps evaluation" begin + m = 5 + mps = MPS(m) + linkindices = [Index(2, "link") for i in 1:(m-1)] + mps[1] = ITensor( + [i == j for i in 0:1, j in 0:1], + Index(2, "site"), + linkindices[1] + ) + for i in 2:(m-1) + mps[i] = ITensor( + [i == j && j == k for i in 0:1, j in 0:1, k in 0:1], + linkindices[i-1], + Index(2, "site"), + linkindices[i] + ) + end + mps[m] = ITensor( + [i == j for i in 0:1, j in 0:1], + linkindices[m-1], + Index(2, "site") + ) + + for i in 1:(2^m) + q = binary_representation(i, m) .+ 1 + @test evaluate(mps, siteinds(mps), q) == all(q .== first(q)) + @test ( + evaluate(mps, collect(zip(siteinds(mps), q))) == + all(q .== first(q)) + ) + end + end + end +end diff --git a/test/test_mpsutil.jl b/test/test_mpsutil.jl deleted file mode 100644 index b963759..0000000 --- a/test/test_mpsutil.jl +++ /dev/null @@ -1,68 +0,0 @@ -function binary_representation(index::Int, numdigits::Int) - return Int[(index & (1 << (numdigits - i))) != 0 for i in 1:numdigits] -end - -@testset "mps util" begin - @testset "one mps evaluation" begin - m = 5 - mps = ITensors.MPS(m) - linkindices = [ITensors.Index(2, "link") for i in 1:(m-1)] - mps[1] = ITensors.ITensor( - ones(2, 2), - ITensors.Index(2, "site"), - linkindices[1] - ) - for i in 2:(m-1) - mps[i] = ITensors.ITensor( - ones(2, 2, 2), - linkindices[i-1], - ITensors.Index(2, "site"), - linkindices[i] - ) - end - mps[m] = ITensors.ITensor( - ones(2, 2), - linkindices[m-1], - ITensors.Index(2, "site") - ) - - for i in 1:(2^m) - q = binary_representation(i, m) .+ 1 - @test TCI.evaluate(mps, ITensors.siteinds(mps), q) == 2^(m - 1) - @test TCI.evaluate(mps, collect(zip(ITensors.siteinds(mps), q))) == 2^(m - 1) - end - end - - @testset "delta mps evaluation" begin - m = 5 - mps =ITensors.MPS(m) - linkindices = [ITensors.Index(2, "link") for i in 1:(m-1)] - mps[1] = ITensors.ITensor( - [i == j for i in 0:1, j in 0:1], - ITensors.Index(2, "site"), - linkindices[1] - ) - for i in 2:(m-1) - mps[i] = ITensors.ITensor( - [i == j && j == k for i in 0:1, j in 0:1, k in 0:1], - linkindices[i-1], - ITensors.Index(2, "site"), - linkindices[i] - ) - end - mps[m] = ITensors.ITensor( - [i == j for i in 0:1, j in 0:1], - linkindices[m-1], - ITensors.Index(2, "site") - ) - - for i in 1:(2^m) - q = binary_representation(i, m) .+ 1 - @test TCI.evaluate(mps, ITensors.siteinds(mps), q) == all(q .== first(q)) - @test ( - TCI.evaluate(mps, collect(zip(ITensors.siteinds(mps), q))) == - all(q .== first(q)) - ) - end - end -end diff --git a/test/test_ttmpsconversion.jl b/test/test_ttmpsconversion.jl deleted file mode 100644 index f879e18..0000000 --- a/test/test_ttmpsconversion.jl +++ /dev/null @@ -1,22 +0,0 @@ -@testset "TT to MPS conversion" begin - tt = TCI.TensorTrain([rand(1, 4, 4), rand(4, 4, 2), rand(2, 4, 7), rand(7, 4, 1)]) - mps = ITensors.MPS(tt) - @test ITensors.linkdims(mps) == [4, 2, 7] -end - -@testset "TT to MPO conversion and back" begin - tt = TCI.TensorTrain([rand(1, 4, 3, 4), rand(4, 2, 4, 2), rand(2, 5, 1, 7), rand(7, 9, 4, 1)]) - mpo = ITensors.MPO(tt) - @test ITensors.linkdims(mpo) == [4, 2, 7] - @test ITensors.dim.(ITensors.siteinds(mpo)[1]) == [4, 3] - @test ITensors.dim.(ITensors.siteinds(mpo)[2]) == [2, 4] - @test ITensors.dim.(ITensors.siteinds(mpo)[3]) == [5, 1] - @test ITensors.dim.(ITensors.siteinds(mpo)[4]) == [9, 4] - - tt2 = TCI.TensorTrain{Float64,4}(mpo) - @test all(tt2.T .== tt.T) - - sites = reverse.(ITensors.siteinds(mpo)) - ttreverse = TCI.TensorTrain{Float64,4}(mpo, sites=sites) - @test all(permutedims.(ttreverse.T, Ref([1, 3, 2, 4])) .== tt.T) -end