diff --git a/Project.toml b/Project.toml index c5501f82..89e1340c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ITensorNetworks" uuid = "2919e153-833c-4bdc-8836-1ea460a35fc7" +version = "0.14.3" authors = ["Matthew Fishman , Joseph Tindall and contributors"] -version = "0.14.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/docs/make.jl b/docs/make.jl index 84a3147c..e860fa54 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,24 +2,24 @@ using ITensorNetworks: ITensorNetworks using Documenter: Documenter, DocMeta, deploydocs, makedocs DocMeta.setdocmeta!( - ITensorNetworks, :DocTestSetup, :(using ITensorNetworks); recursive=true + ITensorNetworks, :DocTestSetup, :(using ITensorNetworks); recursive = true ) include("make_index.jl") makedocs(; - modules=[ITensorNetworks], - authors="ITensor developers and contributors", - sitename="ITensorNetworks.jl", - format=Documenter.HTML(; - canonical="https://itensor.github.io/ITensorNetworks.jl", - edit_link="main", - assets=["assets/favicon.ico", "assets/extras.css"], - ), - pages=["Home" => "index.md", "Reference" => "reference.md"], - warnonly=true, + modules = [ITensorNetworks], + authors = "ITensor developers and contributors", + sitename = "ITensorNetworks.jl", + format = Documenter.HTML(; + canonical = "https://itensor.github.io/ITensorNetworks.jl", + edit_link = "main", + assets = ["assets/favicon.ico", "assets/extras.css"], + ), + pages = ["Home" => "index.md", "Reference" => "reference.md"], + warnonly = true, ) deploydocs(; - repo="github.com/ITensor/ITensorNetworks.jl", devbranch="main", push_preview=true + repo = "github.com/ITensor/ITensorNetworks.jl", devbranch = "main", push_preview = true ) diff --git a/docs/make_index.jl b/docs/make_index.jl index b2b46b8a..ad2c18ab 100644 --- a/docs/make_index.jl +++ b/docs/make_index.jl @@ -2,20 +2,20 @@ using Literate: Literate using ITensorNetworks: ITensorNetworks function ccq_logo(content) - include_ccq_logo = """ + include_ccq_logo = """ ```@raw html Flatiron Center for Computational Quantum Physics logo. Flatiron Center for Computational Quantum Physics logo. ``` """ - content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) - return content + content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) + return content end Literate.markdown( - joinpath(pkgdir(ITensorNetworks), "examples", "README.jl"), - joinpath(pkgdir(ITensorNetworks), "docs", "src"); - flavor=Literate.DocumenterFlavor(), - name="index", - postprocess=ccq_logo, + joinpath(pkgdir(ITensorNetworks), "examples", "README.jl"), + joinpath(pkgdir(ITensorNetworks), "docs", "src"); + flavor = Literate.DocumenterFlavor(), + name = "index", + postprocess = ccq_logo, ) diff --git a/docs/make_readme.jl b/docs/make_readme.jl index 2324636e..f639d6e1 100644 --- a/docs/make_readme.jl +++ b/docs/make_readme.jl @@ -2,20 +2,20 @@ using Literate: Literate using ITensorNetworks: ITensorNetworks function ccq_logo(content) - include_ccq_logo = """ + include_ccq_logo = """ Flatiron Center for Computational Quantum Physics logo. """ - content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) - return content + content = replace(content, "{CCQ_LOGO}" => include_ccq_logo) + return content end Literate.markdown( - joinpath(pkgdir(ITensorNetworks), "examples", "README.jl"), - joinpath(pkgdir(ITensorNetworks)); - flavor=Literate.CommonMarkFlavor(), - name="README", - postprocess=ccq_logo, + joinpath(pkgdir(ITensorNetworks), "examples", "README.jl"), + joinpath(pkgdir(ITensorNetworks)); + flavor = Literate.CommonMarkFlavor(), + name = "README", + postprocess = ccq_logo, ) diff --git a/examples/README.jl b/examples/README.jl index d4148d43..c18002c9 100644 --- a/examples/README.jl +++ b/examples/README.jl @@ -11,7 +11,7 @@ # > or for us to clearly announce parts of the code we are changing. # # ITensorNetworks.jl -# +# # [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://itensor.github.io/ITensorNetworks.jl/stable/) # [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://itensor.github.io/ITensorNetworks.jl/dev/) # [![Build Status](https://github.com/ITensor/ITensorNetworks.jl/actions/workflows/Tests.yml/badge.svg?branch=main)](https://github.com/ITensor/ITensorNetworks.jl/actions/workflows/Tests.yml?query=branch%3Amain) diff --git a/ext/ITensorNetworksEinExprsExt/ITensorNetworksEinExprsExt.jl b/ext/ITensorNetworksEinExprsExt/ITensorNetworksEinExprsExt.jl index 0eefc740..3ef92be0 100644 --- a/ext/ITensorNetworksEinExprsExt/ITensorNetworksEinExprsExt.jl +++ b/ext/ITensorNetworksEinExprsExt/ITensorNetworksEinExprsExt.jl @@ -2,57 +2,57 @@ module ITensorNetworksEinExprsExt using ITensors: Index, ITensor, @Algorithm_str, inds, noncommoninds using ITensorNetworks: - ITensorNetworks, - ITensorList, - ITensorNetwork, - vertextype, - vertex_data, - contraction_sequence + ITensorNetworks, + ITensorList, + ITensorNetwork, + vertextype, + vertex_data, + contraction_sequence using EinExprs: EinExprs, EinExpr, einexpr, SizedEinExpr function to_einexpr(ts::ITensorList) - IndexType = Any + IndexType = Any - tensor_exprs = EinExpr{IndexType}[] - inds_dims = Dict{IndexType,Int}() + tensor_exprs = EinExpr{IndexType}[] + inds_dims = Dict{IndexType, Int}() - for tensor_v in ts - inds_v = collect(inds(tensor_v)) - push!(tensor_exprs, EinExpr{IndexType}(; head=inds_v)) - merge!(inds_dims, Dict(inds_v .=> size(tensor_v))) - end + for tensor_v in ts + inds_v = collect(inds(tensor_v)) + push!(tensor_exprs, EinExpr{IndexType}(; head = inds_v)) + merge!(inds_dims, Dict(inds_v .=> size(tensor_v))) + end - externalinds_tn = reduce(noncommoninds, ts) - return SizedEinExpr(sum(tensor_exprs; skip=externalinds_tn), inds_dims) + externalinds_tn = reduce(noncommoninds, ts) + return SizedEinExpr(sum(tensor_exprs; skip = externalinds_tn), inds_dims) end function tensor_inds_to_vertex(ts::ITensorList) - IndexType = Any - VertexType = Int + IndexType = Any + VertexType = Int - mapping = Dict{Set{IndexType},VertexType}() + mapping = Dict{Set{IndexType}, VertexType}() - for (v, tensor_v) in enumerate(ts) - inds_v = collect(inds(tensor_v)) - mapping[Set(inds_v)] = v - end + for (v, tensor_v) in enumerate(ts) + inds_v = collect(inds(tensor_v)) + mapping[Set(inds_v)] = v + end - return mapping + return mapping end function ITensorNetworks.contraction_sequence( - ::Algorithm"einexpr", tn::ITensorList; optimizer=EinExprs.Exhaustive() -) - expr = to_einexpr(tn) - path = einexpr(optimizer, expr) - return to_contraction_sequence(path, tensor_inds_to_vertex(tn)) + ::Algorithm"einexpr", tn::ITensorList; optimizer = EinExprs.Exhaustive() + ) + expr = to_einexpr(tn) + path = einexpr(optimizer, expr) + return to_contraction_sequence(path, tensor_inds_to_vertex(tn)) end function to_contraction_sequence(expr, tensor_inds_to_vertex) - EinExprs.nargs(expr) == 0 && return tensor_inds_to_vertex[Set(expr.head)] - return map( - expr -> to_contraction_sequence(expr, tensor_inds_to_vertex), EinExprs.args(expr) - ) + EinExprs.nargs(expr) == 0 && return tensor_inds_to_vertex[Set(expr.head)] + return map( + expr -> to_contraction_sequence(expr, tensor_inds_to_vertex), EinExprs.args(expr) + ) end end diff --git a/ext/ITensorNetworksGraphsFlowsExt/ITensorNetworksGraphsFlowsExt.jl b/ext/ITensorNetworksGraphsFlowsExt/ITensorNetworksGraphsFlowsExt.jl index dfe9fc51..07febe0f 100644 --- a/ext/ITensorNetworksGraphsFlowsExt/ITensorNetworksGraphsFlowsExt.jl +++ b/ext/ITensorNetworksGraphsFlowsExt/ITensorNetworksGraphsFlowsExt.jl @@ -5,15 +5,15 @@ using ITensorNetworks: ITensorNetworks using NDTensors.AlgorithmSelection: @Algorithm_str function ITensorNetworks.mincut( - ::Algorithm"GraphsFlows", - graph::AbstractGraph, - source_vertex, - target_vertex; - capacity_matrix, - alg=GraphsFlows.PushRelabelAlgorithm(), -) - # TODO: Replace with `Backend(backend)`. - return GraphsFlows.mincut(graph, source_vertex, target_vertex, capacity_matrix, alg) + ::Algorithm"GraphsFlows", + graph::AbstractGraph, + source_vertex, + target_vertex; + capacity_matrix, + alg = GraphsFlows.PushRelabelAlgorithm(), + ) + # TODO: Replace with `Backend(backend)`. + return GraphsFlows.mincut(graph, source_vertex, target_vertex, capacity_matrix, alg) end end diff --git a/ext/ITensorNetworksOMEinsumContractionOrdersExt/ITensorNetworksOMEinsumContractionOrdersExt.jl b/ext/ITensorNetworksOMEinsumContractionOrdersExt/ITensorNetworksOMEinsumContractionOrdersExt.jl index 6fc09e57..58171e3a 100644 --- a/ext/ITensorNetworksOMEinsumContractionOrdersExt/ITensorNetworksOMEinsumContractionOrdersExt.jl +++ b/ext/ITensorNetworksOMEinsumContractionOrdersExt/ITensorNetworksOMEinsumContractionOrdersExt.jl @@ -12,29 +12,29 @@ using OMEinsumContractionOrders: OMEinsumContractionOrders # infer the output tensor labels # TODO: Use `symdiff` instead. function infer_output(inputs::AbstractVector{<:AbstractVector{<:Index}}) - indslist = reduce(vcat, inputs) - # get output indices - iy = eltype(eltype(inputs))[] - for l in indslist - c = count(==(l), indslist) - if c == 1 - push!(iy, l) - elseif c !== 2 - error("Each index in a tensor network must appear at most twice!") + indslist = reduce(vcat, inputs) + # get output indices + iy = eltype(eltype(inputs))[] + for l in indslist + c = count(==(l), indslist) + if c == 1 + push!(iy, l) + elseif c !== 2 + error("Each index in a tensor network must appear at most twice!") + end end - end - return iy + return iy end # get a (labels, size_dict) representation of a collection of ITensors function rawcode(tensors::ITensorList) - # we use id as the label - indsAs = [collect(Index{Int}, ITensors.inds(A)) for A in tensors] - ixs = collect.(inds.(tensors)) - unique_labels = unique(reduce(vcat, indsAs)) - size_dict = Dict([x => dim(x) for x in unique_labels]) - index_dict = Dict([x => x for x in unique_labels]) - return OMEinsumContractionOrders.EinCode(ixs, infer_output(indsAs)), size_dict, index_dict + # we use id as the label + indsAs = [collect(Index{Int}, ITensors.inds(A)) for A in tensors] + ixs = collect.(inds.(tensors)) + unique_labels = unique(reduce(vcat, indsAs)) + size_dict = Dict([x => dim(x) for x in unique_labels]) + index_dict = Dict([x => x for x in unique_labels]) + return OMEinsumContractionOrders.EinCode(ixs, infer_output(indsAs)), size_dict, index_dict end """ @@ -50,43 +50,43 @@ julia> net = optimize_contraction([x, y, z]; optimizer=TreeSA()); ``` """ function optimize_contraction_nested_einsum( - tensors::ITensorList; - optimizer::OMEinsumContractionOrders.CodeOptimizer=OMEinsumContractionOrders.TreeSA(), -) - r, size_dict, index_dict = rawcode(tensors) - # merge vectors can speed up contraction order finding - # optimize the permutation of tensors is set to true - res = OMEinsumContractionOrders.optimize_code( - r, size_dict, optimizer, OMEinsumContractionOrders.MergeVectors(), true - ) - if res isa OMEinsumContractionOrders.SlicedEinsum # slicing is not supported! - if length(res.slicing) != 0 - @warn "Slicing is not yet supported by `ITensors`, removing slices..." + tensors::ITensorList; + optimizer::OMEinsumContractionOrders.CodeOptimizer = OMEinsumContractionOrders.TreeSA(), + ) + r, size_dict, index_dict = rawcode(tensors) + # merge vectors can speed up contraction order finding + # optimize the permutation of tensors is set to true + res = OMEinsumContractionOrders.optimize_code( + r, size_dict, optimizer, OMEinsumContractionOrders.MergeVectors(), true + ) + if res isa OMEinsumContractionOrders.SlicedEinsum # slicing is not supported! + if length(res.slicing) != 0 + @warn "Slicing is not yet supported by `ITensors`, removing slices..." + end + res = res.eins end - res = res.eins - end - return res + return res end """ Convert NestedEinsum to contraction sequence, such as `[[1, 2], [3, 4]]`. """ function convert_to_contraction_sequence(net::OMEinsumContractionOrders.NestedEinsum) - if OMEinsumContractionOrders.isleaf(net) - return net.tensorindex - else - return convert_to_contraction_sequence.(net.args) - end + if OMEinsumContractionOrders.isleaf(net) + return net.tensorindex + else + return convert_to_contraction_sequence.(net.args) + end end """ Convert the result of `optimize_contraction` to a contraction sequence. """ function optimize_contraction_sequence( - tensors::ITensorList; optimizer::OMEinsumContractionOrders.CodeOptimizer=TreeSA() -) - res = optimize_contraction_nested_einsum(tensors; optimizer) - return convert_to_contraction_sequence(res) + tensors::ITensorList; optimizer::OMEinsumContractionOrders.CodeOptimizer = TreeSA() + ) + res = optimize_contraction_nested_einsum(tensors; optimizer) + return convert_to_contraction_sequence(res) end """ @@ -100,11 +100,11 @@ The fast but poor greedy optimizer. Input arguments are: * `nrepeat` is the number of repeatition, returns the best contraction order. """ function ITensorNetworks.contraction_sequence( - ::Algorithm"greedy", tn::Vector{ITensor}; kwargs... -) - return optimize_contraction_sequence( - tn; optimizer=OMEinsumContractionOrders.GreedyMethod(; kwargs...) - ) + ::Algorithm"greedy", tn::Vector{ITensor}; kwargs... + ) + return optimize_contraction_sequence( + tn; optimizer = OMEinsumContractionOrders.GreedyMethod(; kwargs...) + ) end """ @@ -125,11 +125,11 @@ Optimize the einsum contraction pattern using the simulated annealing on tensor * [Recursive Multi-Tensor Contraction for XEB Verification of Quantum Circuits](https://arxiv.org/abs/2108.05665) """ function ITensorNetworks.contraction_sequence( - ::Algorithm"tree_sa", tn::ITensorList; kwargs... -) - return optimize_contraction_sequence( - tn; optimizer=OMEinsumContractionOrders.TreeSA(; kwargs...) - ) + ::Algorithm"tree_sa", tn::ITensorList; kwargs... + ) + return optimize_contraction_sequence( + tn; optimizer = OMEinsumContractionOrders.TreeSA(; kwargs...) + ) end """ @@ -154,11 +154,11 @@ Then finds the contraction order inside each group with the greedy search algori * [Hyper-optimized tensor network contraction](https://arxiv.org/abs/2002.01935) """ function ITensorNetworks.contraction_sequence( - ::Algorithm"sa_bipartite", tn::ITensorList; kwargs... -) - return optimize_contraction_sequence( - tn; optimizer=OMEinsumContractionOrders.SABipartite(; kwargs...) - ) + ::Algorithm"sa_bipartite", tn::ITensorList; kwargs... + ) + return optimize_contraction_sequence( + tn; optimizer = OMEinsumContractionOrders.SABipartite(; kwargs...) + ) end """ @@ -180,10 +180,10 @@ Then finds the contraction order inside each group with the greedy search algori * [Simulating the Sycamore quantum supremacy circuits](https://arxiv.org/abs/2103.03074) """ function ITensorNetworks.contraction_sequence( - ::Algorithm"kahypar_bipartite", tn::ITensorList; kwargs... -) - return optimize_contraction_sequence( - tn; optimizer=OMEinsumContractionOrders.KaHyParBipartite(; kwargs...) - ) + ::Algorithm"kahypar_bipartite", tn::ITensorList; kwargs... + ) + return optimize_contraction_sequence( + tn; optimizer = OMEinsumContractionOrders.KaHyParBipartite(; kwargs...) + ) end end diff --git a/ext/ITensorNetworksObserversExt/ITensorNetworksObserversExt.jl b/ext/ITensorNetworksObserversExt/ITensorNetworksObserversExt.jl index e5565d21..340d3d9e 100644 --- a/ext/ITensorNetworksObserversExt/ITensorNetworksObserversExt.jl +++ b/ext/ITensorNetworksObserversExt/ITensorNetworksObserversExt.jl @@ -4,6 +4,6 @@ using Observers.DataFrames: AbstractDataFrame using Observers: Observers function ITensorNetworks.update_observer!(observer::AbstractDataFrame; kwargs...) - return Observers.update!(observer; kwargs...) + return Observers.update!(observer; kwargs...) end end diff --git a/ext/ITensorNetworksTensorOperationsExt/ITensorNetworksTensorOperationsExt.jl b/ext/ITensorNetworksTensorOperationsExt/ITensorNetworksTensorOperationsExt.jl index 242b0201..47005036 100644 --- a/ext/ITensorNetworksTensorOperationsExt/ITensorNetworksTensorOperationsExt.jl +++ b/ext/ITensorNetworksTensorOperationsExt/ITensorNetworksTensorOperationsExt.jl @@ -6,11 +6,11 @@ using NDTensors.AlgorithmSelection: @Algorithm_str using TensorOperations: TensorOperations, optimaltree function ITensorNetworks.contraction_sequence(::Algorithm"optimal", tn::ITensorList) - network = collect.(inds.(tn)) - #Converting dims to Float64 to minimize overflow issues - inds_to_dims = Dict(i => Float64(dim(i)) for i in unique(reduce(vcat, network))) - seq, _ = optimaltree(network, inds_to_dims) - return seq + network = collect.(inds.(tn)) + #Converting dims to Float64 to minimize overflow issues + inds_to_dims = Dict(i => Float64(dim(i)) for i in unique(reduce(vcat, network))) + seq, _ = optimaltree(network, inds_to_dims) + return seq end end diff --git a/src/abstractindsnetwork.jl b/src/abstractindsnetwork.jl index eda958f6..5775e4bd 100644 --- a/src/abstractindsnetwork.jl +++ b/src/abstractindsnetwork.jl @@ -6,7 +6,7 @@ using .ITensorsExtensions: ITensorsExtensions, promote_indtype using NamedGraphs: NamedGraphs using NamedGraphs.GraphsExtensions: incident_edges, rename_vertices -abstract type AbstractIndsNetwork{V,I} <: AbstractDataGraph{V,Vector{I},Vector{I}} end +abstract type AbstractIndsNetwork{V, I} <: AbstractDataGraph{V, Vector{I}, Vector{I}} end # Field access data_graph(graph::AbstractIndsNetwork) = not_implemented() @@ -16,136 +16,136 @@ Graphs.is_directed(::Type{<:AbstractIndsNetwork}) = false # AbstractDataGraphs overloads function DataGraphs.vertex_data(graph::AbstractIndsNetwork, args...) - return vertex_data(data_graph(graph), args...) + return vertex_data(data_graph(graph), args...) end function DataGraphs.edge_data(graph::AbstractIndsNetwork, args...) - return edge_data(data_graph(graph), args...) + return edge_data(data_graph(graph), args...) end # TODO: Define a generic fallback for `AbstractDataGraph`? -DataGraphs.edge_data_eltype(::Type{<:AbstractIndsNetwork{V,I}}) where {V,I} = Vector{I} +DataGraphs.edge_data_eltype(::Type{<:AbstractIndsNetwork{V, I}}) where {V, I} = Vector{I} ## TODO: Bring these back. ## function indsnetwork_getindex(is::AbstractIndsNetwork, index) ## return get(data_graph(is), index, indtype(is)[]) ## end -## +## ## function Base.getindex(is::AbstractIndsNetwork, index) ## return indsnetwork_getindex(is, index) ## end -## +## ## function Base.getindex(is::AbstractIndsNetwork, index::Pair) ## return indsnetwork_getindex(is, index) ## end -## +## ## function Base.getindex(is::AbstractIndsNetwork, index::AbstractEdge) ## return indsnetwork_getindex(is, index) ## end -## +## ## function indsnetwork_setindex!(is::AbstractIndsNetwork, value, index) ## data_graph(is)[index] = value ## return is ## end -## +## ## function Base.setindex!(is::AbstractIndsNetwork, value, index) ## indsnetwork_setindex!(is, value, index) ## return is ## end -## +## ## function Base.setindex!(is::AbstractIndsNetwork, value, index::Pair) ## indsnetwork_setindex!(is, value, index) ## return is ## end -## +## ## function Base.setindex!(is::AbstractIndsNetwork, value, index::AbstractEdge) ## indsnetwork_setindex!(is, value, index) ## return is ## end -## +## ## function Base.setindex!(is::AbstractIndsNetwork, value::Index, index) ## indsnetwork_setindex!(is, value, index) ## return is ## end -# +# # Index access -# +# function ITensors.uniqueinds(is::AbstractIndsNetwork, edge::AbstractEdge) - # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. - inds = IndexSet(get(is, src(edge), Index[])) - for ei in setdiff(incident_edges(is, src(edge)), [edge]) # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. - inds = unioninds(inds, get(is, ei, Index[])) - end - return inds + inds = IndexSet(get(is, src(edge), Index[])) + for ei in setdiff(incident_edges(is, src(edge)), [edge]) + # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. + inds = unioninds(inds, get(is, ei, Index[])) + end + return inds end function ITensors.uniqueinds(is::AbstractIndsNetwork, edge::Pair) - return uniqueinds(is, edgetype(is)(edge)) + return uniqueinds(is, edgetype(is)(edge)) end function Base.union(is1::AbstractIndsNetwork, is2::AbstractIndsNetwork; kwargs...) - return IndsNetwork(union(data_graph(is1), data_graph(is2); kwargs...)) + return IndsNetwork(union(data_graph(is1), data_graph(is2); kwargs...)) end function NamedGraphs.rename_vertices(f::Function, tn::AbstractIndsNetwork) - return IndsNetwork(rename_vertices(f, data_graph(tn))) + return IndsNetwork(rename_vertices(f, data_graph(tn))) end -# +# # Convenience functions -# +# function ITensorsExtensions.promote_indtypeof(is::AbstractIndsNetwork) - sitetype = mapreduce(promote_indtype, vertices(is); init=Index{Int}) do v - # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. - return mapreduce(typeof, promote_indtype, get(is, v, Index[]); init=Index{Int}) - end - linktype = mapreduce(promote_indtype, edges(is); init=Index{Int}) do e - # TODO: Replace with `is[e]` once `getindex(::IndsNetwork, ...)` is smarter. - return mapreduce(typeof, promote_indtype, get(is, e, Index[]); init=Index{Int}) - end - return promote_indtype(sitetype, linktype) + sitetype = mapreduce(promote_indtype, vertices(is); init = Index{Int}) do v + # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. + return mapreduce(typeof, promote_indtype, get(is, v, Index[]); init = Index{Int}) + end + linktype = mapreduce(promote_indtype, edges(is); init = Index{Int}) do e + # TODO: Replace with `is[e]` once `getindex(::IndsNetwork, ...)` is smarter. + return mapreduce(typeof, promote_indtype, get(is, e, Index[]); init = Index{Int}) + end + return promote_indtype(sitetype, linktype) end function union_all_inds(is_in::AbstractIndsNetwork...) - @assert all(map(ug -> ug == underlying_graph(is_in[1]), underlying_graph.(is_in))) - is_out = IndsNetwork(underlying_graph(is_in[1])) - for v in vertices(is_out) - # TODO: Remove this check. - if any(isassigned(is, v) for is in is_in) - # TODO: Change `get` to `getindex`. - is_out[v] = unioninds([get(is, v, Index[]) for is in is_in]...) + @assert all(map(ug -> ug == underlying_graph(is_in[1]), underlying_graph.(is_in))) + is_out = IndsNetwork(underlying_graph(is_in[1])) + for v in vertices(is_out) + # TODO: Remove this check. + if any(isassigned(is, v) for is in is_in) + # TODO: Change `get` to `getindex`. + is_out[v] = unioninds([get(is, v, Index[]) for is in is_in]...) + end end - end - for e in edges(is_out) - # TODO: Remove this check. - if any(isassigned(is, e) for is in is_in) - # TODO: Change `get` to `getindex`. - is_out[e] = unioninds([get(is, e, Index[]) for is in is_in]...) + for e in edges(is_out) + # TODO: Remove this check. + if any(isassigned(is, e) for is in is_in) + # TODO: Change `get` to `getindex`. + is_out[e] = unioninds([get(is, e, Index[]) for is in is_in]...) + end end - end - return is_out + return is_out end function insert_linkinds( - indsnetwork::AbstractIndsNetwork, - edges=edges(indsnetwork); - link_space=trivial_space(indsnetwork), -) - indsnetwork = copy(indsnetwork) - for e in edges - # TODO: Change to check if it is empty. - if !isassigned(indsnetwork, e) - if !isnothing(link_space) - iₑ = Index(link_space, edge_tag(e)) - # TODO: Allow setting with just `Index`. - indsnetwork[e] = [iₑ] - else - indsnetwork[e] = [] - end + indsnetwork::AbstractIndsNetwork, + edges = edges(indsnetwork); + link_space = trivial_space(indsnetwork), + ) + indsnetwork = copy(indsnetwork) + for e in edges + # TODO: Change to check if it is empty. + if !isassigned(indsnetwork, e) + if !isnothing(link_space) + iₑ = Index(link_space, edge_tag(e)) + # TODO: Allow setting with just `Index`. + indsnetwork[e] = [iₑ] + else + indsnetwork[e] = [] + end + end end - end - return indsnetwork + return indsnetwork end diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index 7ce29b54..fc5492f3 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -1,54 +1,54 @@ using Adapt: Adapt, adapt, adapt_structure using DataGraphs: - DataGraphs, edge_data, underlying_graph, underlying_graph_type, vertex_data + DataGraphs, edge_data, underlying_graph, underlying_graph_type, vertex_data using Dictionaries: Dictionary using Graphs: - Graphs, - Graph, - add_edge!, - add_vertex!, - bfs_tree, - center, - dst, - edges, - edgetype, - ne, - neighbors, - rem_edge!, - src, - vertices + Graphs, + Graph, + add_edge!, + add_vertex!, + bfs_tree, + center, + dst, + edges, + edgetype, + ne, + neighbors, + rem_edge!, + src, + vertices using ITensors: - ITensors, - ITensor, - @Algorithm_str, - addtags, - combiner, - commoninds, - commontags, - contract, - dag, - hascommoninds, - noprime, - onehot, - prime, - replaceprime, - setprime, - unioninds, - uniqueinds, - replacetags, - settags, - sim, - swaptags + ITensors, + ITensor, + @Algorithm_str, + addtags, + combiner, + commoninds, + commontags, + contract, + dag, + hascommoninds, + noprime, + onehot, + prime, + replaceprime, + setprime, + unioninds, + uniqueinds, + replacetags, + settags, + sim, + swaptags using .ITensorsExtensions: ITensorsExtensions, indtype, promote_indtype using LinearAlgebra: LinearAlgebra, factorize using MacroTools: @capture using NamedGraphs: NamedGraphs, NamedGraph, not_implemented, steiner_tree using NamedGraphs.GraphsExtensions: - ⊔, directed_graph, incident_edges, rename_vertices, vertextype + ⊔, directed_graph, incident_edges, rename_vertices, vertextype using NDTensors: NDTensors, dim, Algorithm using SplitApplyCombine: flatten -abstract type AbstractITensorNetwork{V} <: AbstractDataGraph{V,ITensor,ITensor} end +abstract type AbstractITensorNetwork{V} <: AbstractDataGraph{V, ITensor, ITensor} end # Field access data_graph_type(::Type{<:AbstractITensorNetwork}) = not_implemented() @@ -59,14 +59,14 @@ DataGraphs.edge_data_eltype(::Type{<:AbstractITensorNetwork}) = ITensor # Graphs.jl overloads function Graphs.weights(graph::AbstractITensorNetwork) - V = vertextype(graph) - es = Tuple.(edges(graph)) - ws = Dictionary{Tuple{V,V},Float64}(es, undef) - for e in edges(graph) - w = log2(dim(commoninds(graph, e))) - ws[(src(e), dst(e))] = w - end - return ws + V = vertextype(graph) + es = Tuple.(edges(graph)) + ws = Dictionary{Tuple{V, V}, Float64}(es, undef) + for e in edges(graph) + w = log2(dim(commoninds(graph, e))) + ws[(src(e), dst(e))] = w + end + return ws end # Copy @@ -84,38 +84,38 @@ Graphs.is_directed(::Type{<:AbstractITensorNetwork}) = false # Derived interface, may need to be overloaded function DataGraphs.underlying_graph_type(G::Type{<:AbstractITensorNetwork}) - return underlying_graph_type(data_graph_type(G)) + return underlying_graph_type(data_graph_type(G)) end function ITensors.datatype(tn::AbstractITensorNetwork) - return mapreduce(v -> datatype(tn[v]), promote_type, vertices(tn)) + return mapreduce(v -> datatype(tn[v]), promote_type, vertices(tn)) end # AbstractDataGraphs overloads function DataGraphs.vertex_data(graph::AbstractITensorNetwork, args...) - return vertex_data(data_graph(graph), args...) + return vertex_data(data_graph(graph), args...) end function DataGraphs.edge_data(graph::AbstractITensorNetwork, args...) - return edge_data(data_graph(graph), args...) + return edge_data(data_graph(graph), args...) end DataGraphs.underlying_graph(tn::AbstractITensorNetwork) = underlying_graph(data_graph(tn)) function NamedGraphs.vertex_positions(tn::AbstractITensorNetwork) - return NamedGraphs.vertex_positions(underlying_graph(tn)) + return NamedGraphs.vertex_positions(underlying_graph(tn)) end function NamedGraphs.ordered_vertices(tn::AbstractITensorNetwork) - return NamedGraphs.ordered_vertices(underlying_graph(tn)) + return NamedGraphs.ordered_vertices(underlying_graph(tn)) end function Adapt.adapt_structure(to, tn::AbstractITensorNetwork) - # TODO: Define and use: - # - # @preserve_graph map_vertex_data(adapt(to), tn) - # - # or just: - # - # @preserve_graph map(adapt(to), tn) - return map_vertex_data_preserve_graph(adapt(to), tn) + # TODO: Define and use: + # + # @preserve_graph map_vertex_data(adapt(to), tn) + # + # or just: + # + # @preserve_graph map(adapt(to), tn) + return map_vertex_data_preserve_graph(adapt(to), tn) end # @@ -129,22 +129,22 @@ end # TODO: broadcasting function Base.union(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork; kwargs...) - # TODO: Use a different constructor call here? - tn = _ITensorNetwork(union(data_graph(tn1), data_graph(tn2)); kwargs...) - # Add any new edges that are introduced during the union - for v1 in vertices(tn1) - for v2 in vertices(tn2) - if hascommoninds(tn, v1 => v2) - add_edge!(tn, v1 => v2) - end + # TODO: Use a different constructor call here? + tn = _ITensorNetwork(union(data_graph(tn1), data_graph(tn2)); kwargs...) + # Add any new edges that are introduced during the union + for v1 in vertices(tn1) + for v2 in vertices(tn2) + if hascommoninds(tn, v1 => v2) + add_edge!(tn, v1 => v2) + end + end end - end - return tn + return tn end function NamedGraphs.rename_vertices(f::Function, tn::AbstractITensorNetwork) - # TODO: Use a different constructor call here? - return _ITensorNetwork(rename_vertices(f, data_graph(tn))) + # TODO: Use a different constructor call here? + return _ITensorNetwork(rename_vertices(f, data_graph(tn))) end # @@ -152,13 +152,13 @@ end # function setindex_preserve_graph!(tn::AbstractITensorNetwork, value, vertex) - data_graph(tn)[vertex] = value - return tn + data_graph(tn)[vertex] = value + return tn end # TODO: Move to `BaseExtensions` module. function is_setindex!_expr(expr::Expr) - return is_assignment_expr(expr) && is_getindex_expr(first(expr.args)) + return is_assignment_expr(expr) && is_getindex_expr(first(expr.args)) end is_setindex!_expr(x) = false is_getindex_expr(expr::Expr) = (expr.head === :ref) @@ -171,43 +171,43 @@ is_assignment_expr(expr) = false # preserve_graph_function(::typeof(map_vertex_data)) = map_vertex_data_preserve_graph # Also allow annotating codeblocks like `@views`. macro preserve_graph(expr) - if !is_setindex!_expr(expr) - error( - "preserve_graph must be used with setindex! syntax (as @preserve_graph a[i,j,...] = value)", - ) - end - @capture(expr, array_[indices__] = value_) - return :(setindex_preserve_graph!($(esc(array)), $(esc(value)), $(esc.(indices)...))) + if !is_setindex!_expr(expr) + error( + "preserve_graph must be used with setindex! syntax (as @preserve_graph a[i,j,...] = value)", + ) + end + @capture(expr, array_[indices__] = value_) + return :(setindex_preserve_graph!($(esc(array)), $(esc(value)), $(esc.(indices)...))) end function ITensors.hascommoninds(tn::AbstractITensorNetwork, edge::Pair) - return hascommoninds(tn, edgetype(tn)(edge)) + return hascommoninds(tn, edgetype(tn)(edge)) end function ITensors.hascommoninds(tn::AbstractITensorNetwork, edge::AbstractEdge) - return hascommoninds(tn[src(edge)], tn[dst(edge)]) + return hascommoninds(tn[src(edge)], tn[dst(edge)]) end function Base.setindex!(tn::AbstractITensorNetwork, value, v) - # v = to_vertex(tn, index...) - @preserve_graph tn[v] = value - for edge in incident_edges(tn, v) - rem_edge!(tn, edge) - end - for vertex in vertices(tn) - if v ≠ vertex - edge = v => vertex - if hascommoninds(tn, edge) - add_edge!(tn, edge) - end - end - end - return tn + # v = to_vertex(tn, index...) + @preserve_graph tn[v] = value + for edge in incident_edges(tn, v) + rem_edge!(tn, edge) + end + for vertex in vertices(tn) + if v ≠ vertex + edge = v => vertex + if hascommoninds(tn, edge) + add_edge!(tn, edge) + end + end + end + return tn end # Convenience wrapper -function eachtensor(tn::AbstractITensorNetwork, vertices=vertices(tn)) - return map(v -> tn[v], vertices) +function eachtensor(tn::AbstractITensorNetwork, vertices = vertices(tn)) + return map(v -> tn[v], vertices) end # @@ -215,26 +215,26 @@ end # function ITensorsExtensions.promote_indtypeof(tn::AbstractITensorNetwork) - return mapreduce(promote_indtype, eachtensor(tn)) do t - return indtype(t) - end + return mapreduce(promote_indtype, eachtensor(tn)) do t + return indtype(t) + end end function NDTensors.scalartype(tn::AbstractITensorNetwork) - return mapreduce(eltype, promote_type, eachtensor(tn); init=Bool) + return mapreduce(eltype, promote_type, eachtensor(tn); init = Bool) end # TODO: Define `eltype(::AbstractITensorNetwork)` as `ITensor`? # TODO: Implement using `adapt` function NDTensors.convert_scalartype(eltype::Type{<:Number}, tn::AbstractITensorNetwork) - tn = copy(tn) - vertex_data(tn) .= ITensors.adapt.(Ref(eltype), vertex_data(tn)) - return tn + tn = copy(tn) + vertex_data(tn) .= ITensors.adapt.(Ref(eltype), vertex_data(tn)) + return tn end function Base.complex(tn::AbstractITensorNetwork) - return NDTensors.convert_scalartype(complex(scalartype(tn)), tn) + return NDTensors.convert_scalartype(complex(scalartype(tn)), tn) end # @@ -242,11 +242,11 @@ end # function Graphs.Graph(tn::AbstractITensorNetwork) - return Graph(Vector{ITensor}(tn)) + return Graph(Vector{ITensor}(tn)) end function NamedGraphs.NamedGraph(tn::AbstractITensorNetwork) - return NamedGraph(Vector{ITensor}(tn)) + return NamedGraph(Vector{ITensor}(tn)) end # @@ -255,14 +255,14 @@ end # Convert to an IndsNetwork function IndsNetwork(tn::AbstractITensorNetwork) - is = IndsNetwork(underlying_graph(tn)) - for v in vertices(tn) - is[v] = uniqueinds(tn, v) - end - for e in edges(tn) - is[e] = commoninds(tn, e) - end - return is + is = IndsNetwork(underlying_graph(tn)) + for v in vertices(tn) + is[v] = uniqueinds(tn, v) + end + for e in edges(tn) + is[e] = commoninds(tn, e) + end + return is end # Alias @@ -271,29 +271,29 @@ indsnetwork(tn::AbstractITensorNetwork) = IndsNetwork(tn) # TODO: Output a `VertexDataGraph`? Unfortunately # `IndsNetwork` doesn't allow iterating over vertex data. function siteinds(tn::AbstractITensorNetwork) - is = IndsNetwork(underlying_graph(tn)) - for v in vertices(tn) - is[v] = uniqueinds(tn, v) - end - return is + is = IndsNetwork(underlying_graph(tn)) + for v in vertices(tn) + is[v] = uniqueinds(tn, v) + end + return is end function flatten_siteinds(tn::AbstractITensorNetwork) - # `identity.(...)` narrows the type, maybe there is a better way. - return identity.(flatten(map(v -> siteinds(tn, v), vertices(tn)))) + # `identity.(...)` narrows the type, maybe there is a better way. + return identity.(flatten(map(v -> siteinds(tn, v), vertices(tn)))) end function linkinds(tn::AbstractITensorNetwork) - is = IndsNetwork(underlying_graph(tn)) - for e in edges(tn) - is[e] = commoninds(tn, e) - end - return is + is = IndsNetwork(underlying_graph(tn)) + for e in edges(tn) + is[e] = commoninds(tn, e) + end + return is end function flatten_linkinds(tn::AbstractITensorNetwork) - # `identity.(...)` narrows the type, maybe there is a better way. - return identity.(flatten(map(e -> linkinds(tn, e), edges(tn)))) + # `identity.(...)` narrows the type, maybe there is a better way. + return identity.(flatten(map(e -> linkinds(tn, e), edges(tn)))) end # @@ -301,187 +301,187 @@ end # function neighbor_tensors(tn::AbstractITensorNetwork, vertex) - return eachtensor(tn, neighbors(tn, vertex)) + return eachtensor(tn, neighbors(tn, vertex)) end function ITensors.uniqueinds(tn::AbstractITensorNetwork, vertex) - tn_vertex = [tn[vertex]; collect(neighbor_tensors(tn, vertex))] - return reduce(setdiff, inds.(tn_vertex)) + tn_vertex = [tn[vertex]; collect(neighbor_tensors(tn, vertex))] + return reduce(setdiff, inds.(tn_vertex)) end function ITensors.uniqueinds(tn::AbstractITensorNetwork, edge::AbstractEdge) - return uniqueinds(tn[src(edge)], tn[dst(edge)]) + return uniqueinds(tn[src(edge)], tn[dst(edge)]) end function ITensors.uniqueinds(tn::AbstractITensorNetwork, edge::Pair) - return uniqueinds(tn, edgetype(tn)(edge)) + return uniqueinds(tn, edgetype(tn)(edge)) end function siteinds(tn::AbstractITensorNetwork, vertex) - return uniqueinds(tn, vertex) + return uniqueinds(tn, vertex) end # Fix ambiguity error with IndsNetwork constructor. function siteinds(tn::AbstractITensorNetwork, vertex::Int) - return uniqueinds(tn, vertex) + return uniqueinds(tn, vertex) end function ITensors.commoninds(tn::AbstractITensorNetwork, edge) - e = edgetype(tn)(edge) - return commoninds(tn[src(e)], tn[dst(e)]) + e = edgetype(tn)(edge) + return commoninds(tn[src(e)], tn[dst(e)]) end function linkinds(tn::AbstractITensorNetwork, edge) - return commoninds(tn, edge) + return commoninds(tn, edge) end # Priming and tagging (changing Index identifiers) function ITensors.replaceinds( - tn::AbstractITensorNetwork, is_is′::Pair{<:IndsNetwork,<:IndsNetwork} -) - tn = copy(tn) - is, is′ = is_is′ - @assert underlying_graph(is) == underlying_graph(is′) - for v in vertices(is) - isassigned(is, v) || continue - @preserve_graph tn[v] = replaceinds(tn[v], is[v] => is′[v]) - end - for e in edges(is) - isassigned(is, e) || continue - for v in (src(e), dst(e)) - @preserve_graph tn[v] = replaceinds(tn[v], is[e] => is′[e]) - end - end - return tn + tn::AbstractITensorNetwork, is_is′::Pair{<:IndsNetwork, <:IndsNetwork} + ) + tn = copy(tn) + is, is′ = is_is′ + @assert underlying_graph(is) == underlying_graph(is′) + for v in vertices(is) + isassigned(is, v) || continue + @preserve_graph tn[v] = replaceinds(tn[v], is[v] => is′[v]) + end + for e in edges(is) + isassigned(is, e) || continue + for v in (src(e), dst(e)) + @preserve_graph tn[v] = replaceinds(tn[v], is[e] => is′[e]) + end + end + return tn end function map_inds(f, tn::AbstractITensorNetwork, args...; kwargs...) - is = IndsNetwork(tn) - is′ = map_inds(f, is, args...; kwargs...) - return replaceinds(tn, is => is′) + is = IndsNetwork(tn) + is′ = map_inds(f, is, args...; kwargs...) + return replaceinds(tn, is => is′) end const map_inds_label_functions = [ - :prime, - :setprime, - :noprime, - :replaceprime, - # :swapprime, # TODO: add @test_broken as a reminder - :addtags, - :removetags, - :replacetags, - :settags, - :sim, - :swaptags, - :dag, - # :replaceind, - # :replaceinds, - # :swapind, - # :swapinds, + :prime, + :setprime, + :noprime, + :replaceprime, + # :swapprime, # TODO: add @test_broken as a reminder + :addtags, + :removetags, + :replacetags, + :settags, + :sim, + :swaptags, + :dag, + # :replaceind, + # :replaceinds, + # :swapind, + # :swapinds, ] for f in map_inds_label_functions - @eval begin - function ITensors.$f(n::Union{IndsNetwork,AbstractITensorNetwork}, args...; kwargs...) - return map_inds($f, n, args...; kwargs...) - end - - function ITensors.$f( - ffilter::typeof(linkinds), - n::Union{IndsNetwork,AbstractITensorNetwork}, - args...; - kwargs..., - ) - return map_inds($f, n, args...; sites=[], kwargs...) - end - - function ITensors.$f( - ffilter::typeof(siteinds), - n::Union{IndsNetwork,AbstractITensorNetwork}, - args...; - kwargs..., - ) - return map_inds($f, n, args...; links=[], kwargs...) + @eval begin + function ITensors.$f(n::Union{IndsNetwork, AbstractITensorNetwork}, args...; kwargs...) + return map_inds($f, n, args...; kwargs...) + end + + function ITensors.$f( + ffilter::typeof(linkinds), + n::Union{IndsNetwork, AbstractITensorNetwork}, + args...; + kwargs..., + ) + return map_inds($f, n, args...; sites = [], kwargs...) + end + + function ITensors.$f( + ffilter::typeof(siteinds), + n::Union{IndsNetwork, AbstractITensorNetwork}, + args...; + kwargs..., + ) + return map_inds($f, n, args...; links = [], kwargs...) + end end - end end -LinearAlgebra.adjoint(tn::Union{IndsNetwork,AbstractITensorNetwork}) = prime(tn) +LinearAlgebra.adjoint(tn::Union{IndsNetwork, AbstractITensorNetwork}) = prime(tn) function map_vertex_data(f, tn::AbstractITensorNetwork) - tn = copy(tn) - for v in vertices(tn) - tn[v] = f(tn[v]) - end - return tn + tn = copy(tn) + for v in vertices(tn) + tn[v] = f(tn[v]) + end + return tn end # TODO: Define @preserve_graph map_vertex_data(f, tn)` function map_vertex_data_preserve_graph(f, tn::AbstractITensorNetwork) - tn = copy(tn) - for v in vertices(tn) - @preserve_graph tn[v] = f(tn[v]) - end - return tn + tn = copy(tn) + for v in vertices(tn) + @preserve_graph tn[v] = f(tn[v]) + end + return tn end -function map_vertices_preserve_graph!(f, tn::AbstractITensorNetwork; vertices=vertices(tn)) - for v in vertices - @preserve_graph tn[v] = f(v) - end - return tn +function map_vertices_preserve_graph!(f, tn::AbstractITensorNetwork; vertices = vertices(tn)) + for v in vertices + @preserve_graph tn[v] = f(v) + end + return tn end function Base.conj(tn::AbstractITensorNetwork) - # TODO: Use `@preserve_graph map_vertex_data(f, tn)` - return map_vertex_data_preserve_graph(conj, tn) + # TODO: Use `@preserve_graph map_vertex_data(f, tn)` + return map_vertex_data_preserve_graph(conj, tn) end function ITensors.dag(tn::AbstractITensorNetwork) - # TODO: Use `@preserve_graph map_vertex_data(f, tn)` - return map_vertex_data_preserve_graph(dag, tn) + # TODO: Use `@preserve_graph map_vertex_data(f, tn)` + return map_vertex_data_preserve_graph(dag, tn) end # TODO: should this make sure that internal indices # don't clash? function ⊗( - tn1::AbstractITensorNetwork, - tn2::AbstractITensorNetwork, - tn_tail::AbstractITensorNetwork...; - kwargs..., -) - return ⊔(tn1, tn2, tn_tail...; kwargs...) + tn1::AbstractITensorNetwork, + tn2::AbstractITensorNetwork, + tn_tail::AbstractITensorNetwork...; + kwargs..., + ) + return ⊔(tn1, tn2, tn_tail...; kwargs...) end function ⊗( - tn1::Pair{<:Any,<:AbstractITensorNetwork}, - tn2::Pair{<:Any,<:AbstractITensorNetwork}, - tn_tail::Pair{<:Any,<:AbstractITensorNetwork}...; - kwargs..., -) - return ⊔(tn1, tn2, tn_tail...; kwargs...) + tn1::Pair{<:Any, <:AbstractITensorNetwork}, + tn2::Pair{<:Any, <:AbstractITensorNetwork}, + tn_tail::Pair{<:Any, <:AbstractITensorNetwork}...; + kwargs..., + ) + return ⊔(tn1, tn2, tn_tail...; kwargs...) end # TODO: how to define this lazily? #norm(tn::AbstractITensorNetwork) = sqrt(inner(tn, tn)) function Base.isapprox( - x::AbstractITensorNetwork, - y::AbstractITensorNetwork; - atol::Real=0, - rtol::Real=Base.rtoldefault(scalartype(x), scalartype(y), atol), -) - error("Not implemented") - d = norm(x - y) - if !isfinite(d) - error( - "In `isapprox(x::AbstractITensorNetwork, y::AbstractITensorNetwork)`, `norm(x - y)` is not finite", + x::AbstractITensorNetwork, + y::AbstractITensorNetwork; + atol::Real = 0, + rtol::Real = Base.rtoldefault(scalartype(x), scalartype(y), atol), ) - end - return d <= max(atol, rtol * max(norm(x), norm(y))) + error("Not implemented") + d = norm(x - y) + if !isfinite(d) + error( + "In `isapprox(x::AbstractITensorNetwork, y::AbstractITensorNetwork)`, `norm(x - y)` is not finite", + ) + end + return d <= max(atol, rtol * max(norm(x), norm(y))) end function ITensors.contract(tn::AbstractITensorNetwork, edge::Pair; kwargs...) - return contract(tn, edgetype(tn)(edge); kwargs...) + return contract(tn, edgetype(tn)(edge); kwargs...) end # Contract the tensors at vertices `src(edge)` and `dst(edge)` @@ -490,299 +490,299 @@ end # TODO: write this in terms of a more generic function # `Graphs.merge_vertices!` (https://github.com/mtfishman/ITensorNetworks.jl/issues/12) function NDTensors.contract( - tn::AbstractITensorNetwork, edge::AbstractEdge; merged_vertex=dst(edge) -) - V = promote_type(vertextype(tn), typeof(merged_vertex)) - # TODO: Check `ITensorNetwork{V}`, shouldn't need a copy here. - tn = ITensorNetwork{V}(copy(tn)) - neighbors_src = setdiff(neighbors(tn, src(edge)), [dst(edge)]) - neighbors_dst = setdiff(neighbors(tn, dst(edge)), [src(edge)]) - new_itensor = tn[src(edge)] * tn[dst(edge)] - # The following is equivalent to: - # - # tn[dst(edge)] = new_itensor - # - # but without having to search all vertices - # to update the edges. - rem_vertex!(tn, src(edge)) - rem_vertex!(tn, dst(edge)) - add_vertex!(tn, merged_vertex) - for n_src in neighbors_src - add_edge!(tn, merged_vertex => n_src) - end - for n_dst in neighbors_dst - add_edge!(tn, merged_vertex => n_dst) - end - @preserve_graph tn[merged_vertex] = new_itensor - return tn + tn::AbstractITensorNetwork, edge::AbstractEdge; merged_vertex = dst(edge) + ) + V = promote_type(vertextype(tn), typeof(merged_vertex)) + # TODO: Check `ITensorNetwork{V}`, shouldn't need a copy here. + tn = ITensorNetwork{V}(copy(tn)) + neighbors_src = setdiff(neighbors(tn, src(edge)), [dst(edge)]) + neighbors_dst = setdiff(neighbors(tn, dst(edge)), [src(edge)]) + new_itensor = tn[src(edge)] * tn[dst(edge)] + # The following is equivalent to: + # + # tn[dst(edge)] = new_itensor + # + # but without having to search all vertices + # to update the edges. + rem_vertex!(tn, src(edge)) + rem_vertex!(tn, dst(edge)) + add_vertex!(tn, merged_vertex) + for n_src in neighbors_src + add_edge!(tn, merged_vertex => n_src) + end + for n_dst in neighbors_dst + add_edge!(tn, merged_vertex => n_dst) + end + @preserve_graph tn[merged_vertex] = new_itensor + return tn end function ITensors.tags(tn::AbstractITensorNetwork, edge) - is = linkinds(tn, edge) - return commontags(is) + is = linkinds(tn, edge) + return commontags(is) end function LinearAlgebra.svd(tn::AbstractITensorNetwork, edge::Pair; kwargs...) - return svd(tn, edgetype(tn)(edge)) + return svd(tn, edgetype(tn)(edge)) end function LinearAlgebra.svd( - tn::AbstractITensorNetwork, - edge::AbstractEdge; - U_vertex=src(edge), - S_vertex=(edge, "S"), - V_vertex=(edge, "V"), - u_tags=tags(tn, edge), - v_tags=tags(tn, edge), - kwargs..., -) - tn = copy(tn) - left_inds = uniqueinds(tn, edge) - U, S, V = svd(tn[src(edge)], left_inds; lefttags=u_tags, righttags=v_tags, kwargs...) + tn::AbstractITensorNetwork, + edge::AbstractEdge; + U_vertex = src(edge), + S_vertex = (edge, "S"), + V_vertex = (edge, "V"), + u_tags = tags(tn, edge), + v_tags = tags(tn, edge), + kwargs..., + ) + tn = copy(tn) + left_inds = uniqueinds(tn, edge) + U, S, V = svd(tn[src(edge)], left_inds; lefttags = u_tags, righttags = v_tags, kwargs...) - rem_vertex!(tn, src(edge)) - add_vertex!(tn, U_vertex) - tn[U_vertex] = U + rem_vertex!(tn, src(edge)) + add_vertex!(tn, U_vertex) + tn[U_vertex] = U - add_vertex!(tn, S_vertex) - tn[S_vertex] = S + add_vertex!(tn, S_vertex) + tn[S_vertex] = S - add_vertex!(tn, V_vertex) - tn[V_vertex] = V + add_vertex!(tn, V_vertex) + tn[V_vertex] = V - return tn + return tn end function LinearAlgebra.qr( - tn::AbstractITensorNetwork, - edge::AbstractEdge; - Q_vertex=src(edge), - R_vertex=(edge, "R"), - tags=tags(tn, edge), - kwargs..., -) - tn = copy(tn) - left_inds = uniqueinds(tn, edge) - Q, R = factorize(tn[src(edge)], left_inds; tags, kwargs...) + tn::AbstractITensorNetwork, + edge::AbstractEdge; + Q_vertex = src(edge), + R_vertex = (edge, "R"), + tags = tags(tn, edge), + kwargs..., + ) + tn = copy(tn) + left_inds = uniqueinds(tn, edge) + Q, R = factorize(tn[src(edge)], left_inds; tags, kwargs...) - rem_vertex!(tn, src(edge)) - add_vertex!(tn, Q_vertex) - tn[Q_vertex] = Q + rem_vertex!(tn, src(edge)) + add_vertex!(tn, Q_vertex) + tn[Q_vertex] = Q - add_vertex!(tn, R_vertex) - tn[R_vertex] = R + add_vertex!(tn, R_vertex) + tn[R_vertex] = R - return tn + return tn end function LinearAlgebra.factorize( - tn::AbstractITensorNetwork, - edge::AbstractEdge; - X_vertex=src(edge), - Y_vertex=("Y", edge), - tags=tags(tn, edge), - kwargs..., -) - # Promote vertex type - V = promote_type(vertextype(tn), typeof(X_vertex), typeof(Y_vertex)) - - # TODO: Check `ITensorNetwork{V}`, shouldn't need a copy here. - tn = ITensorNetwork{V}(copy(tn)) - - neighbors_X = setdiff(neighbors(tn, src(edge)), [dst(edge)]) - left_inds = uniqueinds(tn, edge) - X, Y = factorize(tn[src(edge)], left_inds; tags, kwargs...) - - rem_vertex!(tn, src(edge)) - add_vertex!(tn, X_vertex) - add_vertex!(tn, Y_vertex) - - add_edge!(tn, X_vertex => Y_vertex) - for nX in neighbors_X - add_edge!(tn, X_vertex => nX) - end - add_edge!(tn, Y_vertex => dst(edge)) - @preserve_graph tn[X_vertex] = X - @preserve_graph tn[Y_vertex] = Y - return tn + tn::AbstractITensorNetwork, + edge::AbstractEdge; + X_vertex = src(edge), + Y_vertex = ("Y", edge), + tags = tags(tn, edge), + kwargs..., + ) + # Promote vertex type + V = promote_type(vertextype(tn), typeof(X_vertex), typeof(Y_vertex)) + + # TODO: Check `ITensorNetwork{V}`, shouldn't need a copy here. + tn = ITensorNetwork{V}(copy(tn)) + + neighbors_X = setdiff(neighbors(tn, src(edge)), [dst(edge)]) + left_inds = uniqueinds(tn, edge) + X, Y = factorize(tn[src(edge)], left_inds; tags, kwargs...) + + rem_vertex!(tn, src(edge)) + add_vertex!(tn, X_vertex) + add_vertex!(tn, Y_vertex) + + add_edge!(tn, X_vertex => Y_vertex) + for nX in neighbors_X + add_edge!(tn, X_vertex => nX) + end + add_edge!(tn, Y_vertex => dst(edge)) + @preserve_graph tn[X_vertex] = X + @preserve_graph tn[Y_vertex] = Y + return tn end function LinearAlgebra.factorize(tn::AbstractITensorNetwork, edge::Pair; kwargs...) - return factorize(tn, edgetype(tn)(edge); kwargs...) + return factorize(tn, edgetype(tn)(edge); kwargs...) end # For ambiguity error; TODO: decide whether to use graph mutating methods when resulting graph is unchanged? function gauge_edge( - alg::Algorithm"orthogonalize", tn::AbstractITensorNetwork, edge::AbstractEdge; kwargs... -) - # tn = factorize(tn, edge; kwargs...) - # # TODO: Implement as `only(common_neighbors(tn, src(edge), dst(edge)))` - # new_vertex = only(neighbors(tn, src(edge)) ∩ neighbors(tn, dst(edge))) - # return contract(tn, new_vertex => dst(edge)) - !has_edge(tn, edge) && throw(ArgumentError("Edge not in graph.")) - tn = copy(tn) - left_inds = uniqueinds(tn, edge) - ltags = tags(tn, edge) - X, Y = factorize(tn[src(edge)], left_inds; tags=ltags, ortho="left", kwargs...) - @preserve_graph tn[src(edge)] = X - @preserve_graph tn[dst(edge)] = tn[dst(edge)]*Y - return tn + alg::Algorithm"orthogonalize", tn::AbstractITensorNetwork, edge::AbstractEdge; kwargs... + ) + # tn = factorize(tn, edge; kwargs...) + # # TODO: Implement as `only(common_neighbors(tn, src(edge), dst(edge)))` + # new_vertex = only(neighbors(tn, src(edge)) ∩ neighbors(tn, dst(edge))) + # return contract(tn, new_vertex => dst(edge)) + !has_edge(tn, edge) && throw(ArgumentError("Edge not in graph.")) + tn = copy(tn) + left_inds = uniqueinds(tn, edge) + ltags = tags(tn, edge) + X, Y = factorize(tn[src(edge)], left_inds; tags = ltags, ortho = "left", kwargs...) + @preserve_graph tn[src(edge)] = X + @preserve_graph tn[dst(edge)] = tn[dst(edge)] * Y + return tn end # For ambiguity error; TODO: decide whether to use graph mutating methods when resulting graph is unchanged? function gauge_walk( - alg::Algorithm, tn::AbstractITensorNetwork, edges::Vector{<:AbstractEdge}; kwargs... -) - tn = copy(tn) - for edge in edges - tn = gauge_edge(alg, tn, edge; kwargs...) - end - return tn + alg::Algorithm, tn::AbstractITensorNetwork, edges::Vector{<:AbstractEdge}; kwargs... + ) + tn = copy(tn) + for edge in edges + tn = gauge_edge(alg, tn, edge; kwargs...) + end + return tn end function gauge_walk(alg::Algorithm, tn::AbstractITensorNetwork, edge::Pair; kwargs...) - return gauge_edge(alg::Algorithm, tn, edgetype(tn)(edge); kwargs...) + return gauge_edge(alg::Algorithm, tn, edgetype(tn)(edge); kwargs...) end function gauge_walk( - alg::Algorithm, tn::AbstractITensorNetwork, edges::Vector{<:Pair}; kwargs... -) - return gauge_walk(alg, tn, edgetype(tn).(edges); kwargs...) + alg::Algorithm, tn::AbstractITensorNetwork, edges::Vector{<:Pair}; kwargs... + ) + return gauge_walk(alg, tn, edgetype(tn).(edges); kwargs...) end function tree_gauge(alg::Algorithm, ψ::AbstractITensorNetwork, region) - return tree_gauge(alg, ψ, [region]) + return tree_gauge(alg, ψ, [region]) end #Get the path that moves the gauge from a to b on a tree #TODO: Move to NamedGraphs function edge_sequence_between_regions(g::AbstractGraph, region_a::Vector, region_b::Vector) - issetequal(region_a, region_b) && return edgetype(g)[] - st = steiner_tree(g, union(region_a, region_b)) - path = post_order_dfs_edges(st, first(region_b)) - path = filter(e -> !((src(e) ∈ region_b) && (dst(e) ∈ region_b)), path) - return path + issetequal(region_a, region_b) && return edgetype(g)[] + st = steiner_tree(g, union(region_a, region_b)) + path = post_order_dfs_edges(st, first(region_b)) + path = filter(e -> !((src(e) ∈ region_b) && (dst(e) ∈ region_b)), path) + return path end # Gauge a ITensorNetwork from cur_region towards new_region, treating # the network as a tree spanned by a spanning tree. function tree_gauge( - alg::Algorithm, - ψ::AbstractITensorNetwork, - cur_region::Vector, - new_region::Vector; - kwargs..., -) - es = edge_sequence_between_regions(ψ, cur_region, new_region) - ψ = gauge_walk(alg, ψ, es; kwargs...) - return ψ + alg::Algorithm, + ψ::AbstractITensorNetwork, + cur_region::Vector, + new_region::Vector; + kwargs..., + ) + es = edge_sequence_between_regions(ψ, cur_region, new_region) + ψ = gauge_walk(alg, ψ, es; kwargs...) + return ψ end # Gauge a ITensorNetwork towards a region, treating # the network as a tree spanned by a spanning tree. function tree_gauge(alg::Algorithm, ψ::AbstractITensorNetwork, region::Vector) - return tree_gauge(alg, ψ, collect(vertices(ψ)), region) + return tree_gauge(alg, ψ, collect(vertices(ψ)), region) end function tree_orthogonalize(ψ::AbstractITensorNetwork, cur_region, new_region; kwargs...) - return tree_gauge(Algorithm("orthogonalize"), ψ, cur_region, new_region; kwargs...) + return tree_gauge(Algorithm("orthogonalize"), ψ, cur_region, new_region; kwargs...) end function tree_orthogonalize(ψ::AbstractITensorNetwork, region; kwargs...) - return tree_gauge(Algorithm("orthogonalize"), ψ, region; kwargs...) + return tree_gauge(Algorithm("orthogonalize"), ψ, region; kwargs...) end # TODO: decide whether to use graph mutating methods when resulting graph is unchanged? function _truncate_edge(tn::AbstractITensorNetwork, edge::AbstractEdge; kwargs...) - !has_edge(tn, edge) && throw(ArgumentError("Edge not in graph.")) - tn = copy(tn) - left_inds = uniqueinds(tn, edge) - ltags = tags(tn, edge) - U, S, V = svd(tn[src(edge)], left_inds; lefttags=ltags, kwargs...) - @preserve_graph tn[src(edge)] = U - @preserve_graph tn[dst(edge)] = tn[dst(edge)] * (S*V) - return tn + !has_edge(tn, edge) && throw(ArgumentError("Edge not in graph.")) + tn = copy(tn) + left_inds = uniqueinds(tn, edge) + ltags = tags(tn, edge) + U, S, V = svd(tn[src(edge)], left_inds; lefttags = ltags, kwargs...) + @preserve_graph tn[src(edge)] = U + @preserve_graph tn[dst(edge)] = tn[dst(edge)] * (S * V) + return tn end function Base.truncate(tn::AbstractITensorNetwork, edge::AbstractEdge; kwargs...) - return _truncate_edge(tn, edge; kwargs...) + return _truncate_edge(tn, edge; kwargs...) end function Base.truncate(tn::AbstractITensorNetwork, edge::Pair; kwargs...) - return truncate(tn, edgetype(tn)(edge); kwargs...) + return truncate(tn, edgetype(tn)(edge); kwargs...) end function Base.:*(c::Number, ψ::AbstractITensorNetwork) - v₁ = first(vertices(ψ)) - cψ = copy(ψ) - cψ[v₁] *= c - return cψ + v₁ = first(vertices(ψ)) + cψ = copy(ψ) + cψ[v₁] *= c + return cψ end # Return a list of vertices in the ITensorNetwork `ψ` # that share indices with the ITensor `T` function neighbor_vertices(ψ::AbstractITensorNetwork, T::ITensor) - ψT = ψ ⊔ ITensorNetwork([T]) - v⃗ = neighbors(ψT, (1, 2)) - return first.(v⃗) + ψT = ψ ⊔ ITensorNetwork([T]) + v⃗ = neighbors(ψT, (1, 2)) + return first.(v⃗) end -function linkinds_combiners(tn::AbstractITensorNetwork; edges=edges(tn)) - combiners = DataGraph( - directed_graph(underlying_graph(tn)); - vertex_data_eltype=ITensor, - edge_data_eltype=ITensor, - ) - for e in edges - C = combiner(linkinds(tn, e); tags=edge_tag(e)) - combiners[e] = C - combiners[reverse(e)] = dag(C) - end - return combiners +function linkinds_combiners(tn::AbstractITensorNetwork; edges = edges(tn)) + combiners = DataGraph( + directed_graph(underlying_graph(tn)); + vertex_data_eltype = ITensor, + edge_data_eltype = ITensor, + ) + for e in edges + C = combiner(linkinds(tn, e); tags = edge_tag(e)) + combiners[e] = C + combiners[reverse(e)] = dag(C) + end + return combiners end function combine_linkinds(tn::AbstractITensorNetwork, combiners) - combined_tn = copy(tn) - for e in edges(tn) - if !isempty(linkinds(tn, e)) && haskey(edge_data(combiners), e) - combined_tn[src(e)] = combined_tn[src(e)] * combiners[e] - combined_tn[dst(e)] = combined_tn[dst(e)] * combiners[reverse(e)] + combined_tn = copy(tn) + for e in edges(tn) + if !isempty(linkinds(tn, e)) && haskey(edge_data(combiners), e) + combined_tn[src(e)] = combined_tn[src(e)] * combiners[e] + combined_tn[dst(e)] = combined_tn[dst(e)] * combiners[reverse(e)] + end end - end - return combined_tn + return combined_tn end function combine_linkinds( - tn::AbstractITensorNetwork; edges::Vector{<:Union{Pair,AbstractEdge}}=edges(tn) -) - combiners = linkinds_combiners(tn; edges) - return combine_linkinds(tn, combiners) + tn::AbstractITensorNetwork; edges::Vector{<:Union{Pair, AbstractEdge}} = edges(tn) + ) + combiners = linkinds_combiners(tn; edges) + return combine_linkinds(tn, combiners) end function split_index( - tn::AbstractITensorNetwork, - edges_to_split; - src_ind_map::Function=identity, - dst_ind_map::Function=prime, -) - tn = copy(tn) - for e in edges_to_split - inds = commoninds(tn[src(e)], tn[dst(e)]) - tn[src(e)] = replaceinds(tn[src(e)], inds, src_ind_map(inds)) - tn[dst(e)] = replaceinds(tn[dst(e)], inds, dst_ind_map(inds)) - end + tn::AbstractITensorNetwork, + edges_to_split; + src_ind_map::Function = identity, + dst_ind_map::Function = prime, + ) + tn = copy(tn) + for e in edges_to_split + inds = commoninds(tn[src(e)], tn[dst(e)]) + tn[src(e)] = replaceinds(tn[src(e)], inds, src_ind_map(inds)) + tn[dst(e)] = replaceinds(tn[dst(e)], inds, dst_ind_map(inds)) + end - return tn + return tn end function inner_network(x::AbstractITensorNetwork, y::AbstractITensorNetwork; kwargs...) - return LinearFormNetwork(x, y; kwargs...) + return LinearFormNetwork(x, y; kwargs...) end function inner_network( - x::AbstractITensorNetwork, A::AbstractITensorNetwork, y::AbstractITensorNetwork; kwargs... -) - return BilinearFormNetwork(A, x, y; kwargs...) + x::AbstractITensorNetwork, A::AbstractITensorNetwork, y::AbstractITensorNetwork; kwargs... + ) + return BilinearFormNetwork(A, x, y; kwargs...) end norm_sqr_network(ψ::AbstractITensorNetwork) = inner_network(ψ, ψ) @@ -792,18 +792,18 @@ norm_sqr_network(ψ::AbstractITensorNetwork) = inner_network(ψ, ψ) # function Base.show(io::IO, mime::MIME"text/plain", graph::AbstractITensorNetwork) - println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") - show(io, mime, vertices(graph)) - println(io, "\n") - println(io, "and $(ne(graph)) edge(s):") - for e in edges(graph) - show(io, mime, e) + println(io, "$(typeof(graph)) with $(nv(graph)) vertices:") + show(io, mime, vertices(graph)) + println(io, "\n") + println(io, "and $(ne(graph)) edge(s):") + for e in edges(graph) + show(io, mime, e) + println(io) + end println(io) - end - println(io) - println(io, "with vertex data:") - show(io, mime, inds.(vertex_data(graph))) - return nothing + println(io, "with vertex data:") + show(io, mime, inds.(vertex_data(graph))) + return nothing end Base.show(io::IO, graph::AbstractITensorNetwork) = show(io, MIME"text/plain"(), graph) @@ -813,77 +813,77 @@ Base.show(io::IO, graph::AbstractITensorNetwork) = show(io, MIME"text/plain"(), # based on `ITensorVisualizationCore`.). using ITensors.ITensorVisualizationCore: ITensorVisualizationCore, visualize function ITensorVisualizationCore.visualize( - tn::AbstractITensorNetwork, - args...; - vertex_labels_prefix=nothing, - vertex_labels=nothing, - kwargs..., -) - if !isnothing(vertex_labels_prefix) - vertex_labels = [vertex_labels_prefix * string(v) for v in vertices(tn)] - end - # TODO: Use `tokenize_vertex`. - return visualize(collect(eachtensor(tn)), args...; vertex_labels, kwargs...) -end - -# + tn::AbstractITensorNetwork, + args...; + vertex_labels_prefix = nothing, + vertex_labels = nothing, + kwargs..., + ) + if !isnothing(vertex_labels_prefix) + vertex_labels = [vertex_labels_prefix * string(v) for v in vertices(tn)] + end + # TODO: Use `tokenize_vertex`. + return visualize(collect(eachtensor(tn)), args...; vertex_labels, kwargs...) +end + +# # Link dimensions -# +# function maxlinkdim(tn::AbstractITensorNetwork) - md = 1 - for e in edges(tn) - md = max(md, linkdim(tn, e)) - end - return md + md = 1 + for e in edges(tn) + md = max(md, linkdim(tn, e)) + end + return md end function linkdim(tn::AbstractITensorNetwork, edge::Pair) - return linkdim(tn, edgetype(tn)(edge)) + return linkdim(tn, edgetype(tn)(edge)) end function linkdim(tn::AbstractITensorNetwork{V}, edge::AbstractEdge{V}) where {V} - ls = linkinds(tn, edge) - return prod([isnothing(l) ? 1 : dim(l) for l in ls]) + ls = linkinds(tn, edge) + return prod([isnothing(l) ? 1 : dim(l) for l in ls]) end function linkdims(tn::AbstractITensorNetwork{V}) where {V} - ld = DataGraph{V}( - copy(underlying_graph(tn)); vertex_data_eltype=Nothing, edge_data_eltype=Int - ) - for e in edges(ld) - ld[e] = linkdim(tn, e) - end - return ld + ld = DataGraph{V}( + copy(underlying_graph(tn)); vertex_data_eltype = Nothing, edge_data_eltype = Int + ) + for e in edges(ld) + ld[e] = linkdim(tn, e) + end + return ld end -# +# # Site combiners -# +# # TODO: will be broken, fix this function site_combiners(tn::AbstractITensorNetwork{V}) where {V} - Cs = DataGraph{V,ITensor}(copy(underlying_graph(tn))) - for v in vertices(tn) - s = siteinds(tn, v) - Cs[v] = combiner(s; tags=commontags(s)) - end - return Cs + Cs = DataGraph{V, ITensor}(copy(underlying_graph(tn))) + for v in vertices(tn) + s = siteinds(tn, v) + Cs[v] = combiner(s; tags = commontags(s)) + end + return Cs end function insert_linkinds( - tn::AbstractITensorNetwork, edges=edges(tn); link_space=trivial_space(tn) -) - tn = copy(tn) - for e in edges - if !hascommoninds(tn, e) - iₑ = Index(link_space, edge_tag(e)) - X = onehot(iₑ => 1) - tn[src(e)] *= X - tn[dst(e)] *= dag(X) + tn::AbstractITensorNetwork, edges = edges(tn); link_space = trivial_space(tn) + ) + tn = copy(tn) + for e in edges + if !hascommoninds(tn, e) + iₑ = Index(link_space, edge_tag(e)) + X = onehot(iₑ => 1) + tn[src(e)] *= X + tn[dst(e)] *= dag(X) + end end - end - return tn + return tn end # TODO: What to output? Could be an `IndsNetwork`. Or maybe @@ -891,13 +891,13 @@ end # Even in that case, this could output a `Dictionary` # from the edges to the common inds on that edge. function ITensors.commoninds(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork) - inds = Index[] - for v1 in vertices(tn1) - for v2 in vertices(tn2) - append!(inds, commoninds(tn1[v1], tn2[v2])) + inds = Index[] + for v1 in vertices(tn1) + for v2 in vertices(tn2) + append!(inds, commoninds(tn1[v1], tn2[v2])) + end end - end - return inds + return inds end """Check if the edge of an itensornetwork has multiple indices""" @@ -906,82 +906,82 @@ is_multi_edge(tn::AbstractITensorNetwork) = Base.Fix1(is_multi_edge, tn) """Add two itensornetworks together by growing the bond dimension. The network structures need to be have the same vertex names, same site index on each vertex """ function add(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork) - @assert issetequal(vertices(tn1), vertices(tn2)) - - tn1 = combine_linkinds(tn1; edges=filter(is_multi_edge(tn1), edges(tn1))) - tn2 = combine_linkinds(tn2; edges=filter(is_multi_edge(tn2), edges(tn2))) - - edges_tn1, edges_tn2 = edges(tn1), edges(tn2) - - if !issetequal(edges_tn1, edges_tn2) - new_edges = union(edges_tn1, edges_tn2) - tn1 = insert_linkinds(tn1, new_edges) - tn2 = insert_linkinds(tn2, new_edges) - end - - edges_tn1, edges_tn2 = edges(tn1), edges(tn2) - @assert issetequal(edges_tn1, edges_tn2) - - tn12 = copy(tn1) - new_edge_indices = Dict( - zip( - edges_tn1, - [ - Index( - dim(only(linkinds(tn1, e))) + dim(only(linkinds(tn2, e))), - tags(only(linkinds(tn1, e))), - ) for e in edges_tn1 - ], - ), - ) - - #Create vertices of tn12 as direct sum of tn1[v] and tn2[v]. Work out the matching indices by matching edges. Make index tags those of tn1[v] - for v in vertices(tn1) - @assert issetequal(siteinds(tn1, v), siteinds(tn2, v)) - - e1_v = filter(x -> src(x) == v || dst(x) == v, edges_tn1) - e2_v = filter(x -> src(x) == v || dst(x) == v, edges_tn2) - - @assert issetequal(e1_v, e2_v) - tn1v_linkinds = Index[only(linkinds(tn1, e)) for e in e1_v] - tn2v_linkinds = Index[only(linkinds(tn2, e)) for e in e1_v] - tn12v_linkinds = Index[new_edge_indices[e] for e in e1_v] - - @assert length(tn1v_linkinds) == length(tn2v_linkinds) - - tn12[v] = ITensors.directsum( - tn12v_linkinds, - tn1[v] => Tuple(tn1v_linkinds), - tn2[v] => Tuple(tn2v_linkinds); - tags=tags.(Tuple(tn1v_linkinds)), + @assert issetequal(vertices(tn1), vertices(tn2)) + + tn1 = combine_linkinds(tn1; edges = filter(is_multi_edge(tn1), edges(tn1))) + tn2 = combine_linkinds(tn2; edges = filter(is_multi_edge(tn2), edges(tn2))) + + edges_tn1, edges_tn2 = edges(tn1), edges(tn2) + + if !issetequal(edges_tn1, edges_tn2) + new_edges = union(edges_tn1, edges_tn2) + tn1 = insert_linkinds(tn1, new_edges) + tn2 = insert_linkinds(tn2, new_edges) + end + + edges_tn1, edges_tn2 = edges(tn1), edges(tn2) + @assert issetequal(edges_tn1, edges_tn2) + + tn12 = copy(tn1) + new_edge_indices = Dict( + zip( + edges_tn1, + [ + Index( + dim(only(linkinds(tn1, e))) + dim(only(linkinds(tn2, e))), + tags(only(linkinds(tn1, e))), + ) for e in edges_tn1 + ], + ), ) - end - return tn12 + #Create vertices of tn12 as direct sum of tn1[v] and tn2[v]. Work out the matching indices by matching edges. Make index tags those of tn1[v] + for v in vertices(tn1) + @assert issetequal(siteinds(tn1, v), siteinds(tn2, v)) + + e1_v = filter(x -> src(x) == v || dst(x) == v, edges_tn1) + e2_v = filter(x -> src(x) == v || dst(x) == v, edges_tn2) + + @assert issetequal(e1_v, e2_v) + tn1v_linkinds = Index[only(linkinds(tn1, e)) for e in e1_v] + tn2v_linkinds = Index[only(linkinds(tn2, e)) for e in e1_v] + tn12v_linkinds = Index[new_edge_indices[e] for e in e1_v] + + @assert length(tn1v_linkinds) == length(tn2v_linkinds) + + tn12[v] = ITensors.directsum( + tn12v_linkinds, + tn1[v] => Tuple(tn1v_linkinds), + tn2[v] => Tuple(tn2v_linkinds); + tags = tags.(Tuple(tn1v_linkinds)), + ) + end + + return tn12 end """ Scale each tensor of the network via a function vertex -> Number""" function scale!( - weight_function::Function, - tn::AbstractITensorNetwork; - vertices=collect(Graphs.vertices(tn)), -) - return map_vertices_preserve_graph!(v -> weight_function(v) * tn[v], tn; vertices) + weight_function::Function, + tn::AbstractITensorNetwork; + vertices = collect(Graphs.vertices(tn)), + ) + return map_vertices_preserve_graph!(v -> weight_function(v) * tn[v], tn; vertices) end """ Scale each tensor of the network by a scale factor for each vertex in the keys of the dictionary""" function scale!(tn::AbstractITensorNetwork, vertices_weights::Dictionary) - return scale!(v -> vertices_weights[v], tn; vertices=keys(vertices_weights)) + return scale!(v -> vertices_weights[v], tn; vertices = keys(vertices_weights)) end function scale(weight_function::Function, tn; kwargs...) - tn = copy(tn) - return scale!(weight_function, tn; kwargs...) + tn = copy(tn) + return scale!(weight_function, tn; kwargs...) end function scale(tn::AbstractITensorNetwork, vertices_weights::Dictionary; kwargs...) - tn = copy(tn) - return scale!(tn, vertices_weights; kwargs...) + tn = copy(tn) + return scale!(tn, vertices_weights; kwargs...) end Base.:+(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork) = add(tn1, tn2) diff --git a/src/apply.jl b/src/apply.jl index c96e7ad2..70119ed0 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -3,505 +3,505 @@ using Graphs: has_edge using LinearAlgebra: qr using ITensors: Ops using ITensors: - ITensors, - Index, - ITensor, - apply, - commonind, - commoninds, - contract, - dag, - denseblocks, - factorize, - factorize_svd, - hasqns, - isdiag, - noprime, - prime, - replaceind, - replaceinds, - unioninds, - uniqueinds + ITensors, + Index, + ITensor, + apply, + commonind, + commoninds, + contract, + dag, + denseblocks, + factorize, + factorize_svd, + hasqns, + isdiag, + noprime, + prime, + replaceind, + replaceinds, + unioninds, + uniqueinds using KrylovKit: linsolve using LinearAlgebra: eigen, norm, svd using NamedGraphs: NamedEdge, has_edge function full_update_bp( - o::Union{NamedEdge,ITensor}, - ψ, - v⃗; - envs, - nfullupdatesweeps=10, - print_fidelity_loss=false, - envisposdef=false, - callback=Returns(nothing), - symmetrize=false, - apply_kwargs..., -) - outer_dim_v1, outer_dim_v2 = dim(uniqueinds(ψ[v⃗[1]], o, ψ[v⃗[2]])), - dim(uniqueinds(ψ[v⃗[2]], o, ψ[v⃗[1]])) - dim_shared = dim(commoninds(ψ[v⃗[1]], ψ[v⃗[2]])) - d1, d2 = dim(commoninds(ψ[v⃗[1]], o)), dim(commoninds(ψ[v⃗[2]], o)) - if outer_dim_v1 * outer_dim_v2 <= dim_shared * dim_shared * d1 * d2 - Qᵥ₁, Rᵥ₁ = ITensor(true), copy(ψ[v⃗[1]]) - Qᵥ₂, Rᵥ₂ = ITensor(true), copy(ψ[v⃗[2]]) - else - Qᵥ₁, Rᵥ₁ = factorize( - ψ[v⃗[1]], uniqueinds(uniqueinds(ψ[v⃗[1]], ψ[v⃗[2]]), uniqueinds(ψ, v⃗[1])) - ) - Qᵥ₂, Rᵥ₂ = factorize( - ψ[v⃗[2]], uniqueinds(uniqueinds(ψ[v⃗[2]], ψ[v⃗[1]]), uniqueinds(ψ, v⃗[2])) + o::Union{NamedEdge, ITensor}, + ψ, + v⃗; + envs, + nfullupdatesweeps = 10, + print_fidelity_loss = false, + envisposdef = false, + callback = Returns(nothing), + symmetrize = false, + apply_kwargs..., ) - end - extended_envs = vcat(envs, Qᵥ₁, prime(dag(Qᵥ₁)), Qᵥ₂, prime(dag(Qᵥ₂))) - Rᵥ₁, Rᵥ₂ = optimise_p_q( - Rᵥ₁, - Rᵥ₂, - extended_envs, - o; - nfullupdatesweeps, - print_fidelity_loss, - envisposdef, - apply_kwargs..., - ) - if symmetrize - singular_values! = Ref(ITensor()) - Rᵥ₁, Rᵥ₂, spec = factorize_svd( - Rᵥ₁ * Rᵥ₂, - inds(Rᵥ₁); - ortho="none", - tags=edge_tag(v⃗[1] => v⃗[2]), - singular_values!, - apply_kwargs..., + outer_dim_v1, outer_dim_v2 = dim(uniqueinds(ψ[v⃗[1]], o, ψ[v⃗[2]])), + dim(uniqueinds(ψ[v⃗[2]], o, ψ[v⃗[1]])) + dim_shared = dim(commoninds(ψ[v⃗[1]], ψ[v⃗[2]])) + d1, d2 = dim(commoninds(ψ[v⃗[1]], o)), dim(commoninds(ψ[v⃗[2]], o)) + if outer_dim_v1 * outer_dim_v2 <= dim_shared * dim_shared * d1 * d2 + Qᵥ₁, Rᵥ₁ = ITensor(true), copy(ψ[v⃗[1]]) + Qᵥ₂, Rᵥ₂ = ITensor(true), copy(ψ[v⃗[2]]) + else + Qᵥ₁, Rᵥ₁ = factorize( + ψ[v⃗[1]], uniqueinds(uniqueinds(ψ[v⃗[1]], ψ[v⃗[2]]), uniqueinds(ψ, v⃗[1])) + ) + Qᵥ₂, Rᵥ₂ = factorize( + ψ[v⃗[2]], uniqueinds(uniqueinds(ψ[v⃗[2]], ψ[v⃗[1]]), uniqueinds(ψ, v⃗[2])) + ) + end + extended_envs = vcat(envs, Qᵥ₁, prime(dag(Qᵥ₁)), Qᵥ₂, prime(dag(Qᵥ₂))) + Rᵥ₁, Rᵥ₂ = optimise_p_q( + Rᵥ₁, + Rᵥ₂, + extended_envs, + o; + nfullupdatesweeps, + print_fidelity_loss, + envisposdef, + apply_kwargs..., ) - callback(; singular_values=singular_values![], truncation_error=spec.truncerr) - end - ψᵥ₁ = Qᵥ₁ * Rᵥ₁ - ψᵥ₂ = Qᵥ₂ * Rᵥ₂ - return ψᵥ₁, ψᵥ₂ + if symmetrize + singular_values! = Ref(ITensor()) + Rᵥ₁, Rᵥ₂, spec = factorize_svd( + Rᵥ₁ * Rᵥ₂, + inds(Rᵥ₁); + ortho = "none", + tags = edge_tag(v⃗[1] => v⃗[2]), + singular_values!, + apply_kwargs..., + ) + callback(; singular_values = singular_values![], truncation_error = spec.truncerr) + end + ψᵥ₁ = Qᵥ₁ * Rᵥ₁ + ψᵥ₂ = Qᵥ₂ * Rᵥ₂ + return ψᵥ₁, ψᵥ₂ end function simple_update_bp_full( - o::Union{NamedEdge,ITensor}, ψ, v⃗; envs, callback=Returns(nothing), apply_kwargs... -) - cutoff = 10 * eps(real(scalartype(ψ))) - envs_v1 = filter(env -> hascommoninds(env, ψ[v⃗[1]]), envs) - envs_v2 = filter(env -> hascommoninds(env, ψ[v⃗[2]]), envs) - @assert all(ndims(env) == 2 for env in vcat(envs_v1, envs_v2)) - sqrt_envs_v1 = [ - ITensorsExtensions.map_eigvals( - sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v1 - ] - sqrt_envs_v2 = [ - ITensorsExtensions.map_eigvals( - sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v2 - ] - inv_sqrt_envs_v1 = [ - ITensorsExtensions.map_eigvals( - inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v1 - ] - inv_sqrt_envs_v2 = [ - ITensorsExtensions.map_eigvals( - inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v2 - ] - ψᵥ₁ᵥ₂_tn = [ψ[v⃗[1]]; ψ[v⃗[2]]; sqrt_envs_v1; sqrt_envs_v2] - ψᵥ₁ᵥ₂ = contract(ψᵥ₁ᵥ₂_tn; sequence=contraction_sequence(ψᵥ₁ᵥ₂_tn; alg="optimal")) - oψ = apply(o, ψᵥ₁ᵥ₂) - v1_inds = reduce( - vcat, [uniqueinds(sqrt_env_v1, ψ[v⃗[1]]) for sqrt_env_v1 in sqrt_envs_v1]; init=Index[] - ) - v2_inds = reduce( - vcat, [uniqueinds(sqrt_env_v2, ψ[v⃗[2]]) for sqrt_env_v2 in sqrt_envs_v2]; init=Index[] - ) - v1_inds = [v1_inds; siteinds(ψ, v⃗[1])] - v2_inds = [v2_inds; siteinds(ψ, v⃗[2])] - e = v⃗[1] => v⃗[2] - singular_values! = Ref(ITensor()) - ψᵥ₁, ψᵥ₂, spec = factorize_svd( - oψ, v1_inds; ortho="none", tags=edge_tag(e), singular_values!, apply_kwargs... - ) - callback(; singular_values=singular_values![], truncation_error=spec.truncerr) - for inv_sqrt_env_v1 in inv_sqrt_envs_v1 - ψᵥ₁ *= dag(inv_sqrt_env_v1) - end - for inv_sqrt_env_v2 in inv_sqrt_envs_v2 - ψᵥ₂ *= dag(inv_sqrt_env_v2) - end - return ψᵥ₁, ψᵥ₂ + o::Union{NamedEdge, ITensor}, ψ, v⃗; envs, callback = Returns(nothing), apply_kwargs... + ) + cutoff = 10 * eps(real(scalartype(ψ))) + envs_v1 = filter(env -> hascommoninds(env, ψ[v⃗[1]]), envs) + envs_v2 = filter(env -> hascommoninds(env, ψ[v⃗[2]]), envs) + @assert all(ndims(env) == 2 for env in vcat(envs_v1, envs_v2)) + sqrt_envs_v1 = [ + ITensorsExtensions.map_eigvals( + sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v1 + ] + sqrt_envs_v2 = [ + ITensorsExtensions.map_eigvals( + sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v2 + ] + inv_sqrt_envs_v1 = [ + ITensorsExtensions.map_eigvals( + inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v1 + ] + inv_sqrt_envs_v2 = [ + ITensorsExtensions.map_eigvals( + inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v2 + ] + ψᵥ₁ᵥ₂_tn = [ψ[v⃗[1]]; ψ[v⃗[2]]; sqrt_envs_v1; sqrt_envs_v2] + ψᵥ₁ᵥ₂ = contract(ψᵥ₁ᵥ₂_tn; sequence = contraction_sequence(ψᵥ₁ᵥ₂_tn; alg = "optimal")) + oψ = apply(o, ψᵥ₁ᵥ₂) + v1_inds = reduce( + vcat, [uniqueinds(sqrt_env_v1, ψ[v⃗[1]]) for sqrt_env_v1 in sqrt_envs_v1]; init = Index[] + ) + v2_inds = reduce( + vcat, [uniqueinds(sqrt_env_v2, ψ[v⃗[2]]) for sqrt_env_v2 in sqrt_envs_v2]; init = Index[] + ) + v1_inds = [v1_inds; siteinds(ψ, v⃗[1])] + v2_inds = [v2_inds; siteinds(ψ, v⃗[2])] + e = v⃗[1] => v⃗[2] + singular_values! = Ref(ITensor()) + ψᵥ₁, ψᵥ₂, spec = factorize_svd( + oψ, v1_inds; ortho = "none", tags = edge_tag(e), singular_values!, apply_kwargs... + ) + callback(; singular_values = singular_values![], truncation_error = spec.truncerr) + for inv_sqrt_env_v1 in inv_sqrt_envs_v1 + ψᵥ₁ *= dag(inv_sqrt_env_v1) + end + for inv_sqrt_env_v2 in inv_sqrt_envs_v2 + ψᵥ₂ *= dag(inv_sqrt_env_v2) + end + return ψᵥ₁, ψᵥ₂ end # Reduced version function simple_update_bp( - o::Union{NamedEdge,ITensor}, ψ, v⃗; envs, callback=Returns(nothing), apply_kwargs... -) - cutoff = 10 * eps(real(scalartype(ψ))) - envs_v1 = filter(env -> hascommoninds(env, ψ[v⃗[1]]), envs) - envs_v2 = filter(env -> hascommoninds(env, ψ[v⃗[2]]), envs) - @assert all(ndims(env) == 2 for env in vcat(envs_v1, envs_v2)) - sqrt_envs_v1 = [ - ITensorsExtensions.map_eigvals( - sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v1 - ] - sqrt_envs_v2 = [ - ITensorsExtensions.map_eigvals( - sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v2 - ] - inv_sqrt_envs_v1 = [ - ITensorsExtensions.map_eigvals( - inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v1 - ] - inv_sqrt_envs_v2 = [ - ITensorsExtensions.map_eigvals( - inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian=true - ) for env in envs_v2 - ] - ψᵥ₁ = contract([ψ[v⃗[1]]; sqrt_envs_v1]) - ψᵥ₂ = contract([ψ[v⃗[2]]; sqrt_envs_v2]) - sᵥ₁ = siteinds(ψ, v⃗[1]) - sᵥ₂ = siteinds(ψ, v⃗[2]) - Qᵥ₁, Rᵥ₁ = qr(ψᵥ₁, uniqueinds(uniqueinds(ψᵥ₁, ψᵥ₂), sᵥ₁)) - Qᵥ₂, Rᵥ₂ = qr(ψᵥ₂, uniqueinds(uniqueinds(ψᵥ₂, ψᵥ₁), sᵥ₂)) - rᵥ₁ = commoninds(Qᵥ₁, Rᵥ₁) - rᵥ₂ = commoninds(Qᵥ₂, Rᵥ₂) - oR = apply(o, Rᵥ₁ * Rᵥ₂) - e = v⃗[1] => v⃗[2] - singular_values! = Ref(ITensor()) - Rᵥ₁, Rᵥ₂, spec = factorize_svd( - oR, - unioninds(rᵥ₁, sᵥ₁); - ortho="none", - tags=edge_tag(e), - singular_values!, - apply_kwargs..., - ) - callback(; singular_values=singular_values![], truncation_error=spec.truncerr) - Qᵥ₁ = contract([Qᵥ₁; dag.(inv_sqrt_envs_v1)]) - Qᵥ₂ = contract([Qᵥ₂; dag.(inv_sqrt_envs_v2)]) - ψᵥ₁ = Qᵥ₁ * Rᵥ₁ - ψᵥ₂ = Qᵥ₂ * Rᵥ₂ - return ψᵥ₁, ψᵥ₂ + o::Union{NamedEdge, ITensor}, ψ, v⃗; envs, callback = Returns(nothing), apply_kwargs... + ) + cutoff = 10 * eps(real(scalartype(ψ))) + envs_v1 = filter(env -> hascommoninds(env, ψ[v⃗[1]]), envs) + envs_v2 = filter(env -> hascommoninds(env, ψ[v⃗[2]]), envs) + @assert all(ndims(env) == 2 for env in vcat(envs_v1, envs_v2)) + sqrt_envs_v1 = [ + ITensorsExtensions.map_eigvals( + sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v1 + ] + sqrt_envs_v2 = [ + ITensorsExtensions.map_eigvals( + sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v2 + ] + inv_sqrt_envs_v1 = [ + ITensorsExtensions.map_eigvals( + inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v1 + ] + inv_sqrt_envs_v2 = [ + ITensorsExtensions.map_eigvals( + inv ∘ sqrt, env, inds(env)[1], inds(env)[2]; cutoff, ishermitian = true + ) for env in envs_v2 + ] + ψᵥ₁ = contract([ψ[v⃗[1]]; sqrt_envs_v1]) + ψᵥ₂ = contract([ψ[v⃗[2]]; sqrt_envs_v2]) + sᵥ₁ = siteinds(ψ, v⃗[1]) + sᵥ₂ = siteinds(ψ, v⃗[2]) + Qᵥ₁, Rᵥ₁ = qr(ψᵥ₁, uniqueinds(uniqueinds(ψᵥ₁, ψᵥ₂), sᵥ₁)) + Qᵥ₂, Rᵥ₂ = qr(ψᵥ₂, uniqueinds(uniqueinds(ψᵥ₂, ψᵥ₁), sᵥ₂)) + rᵥ₁ = commoninds(Qᵥ₁, Rᵥ₁) + rᵥ₂ = commoninds(Qᵥ₂, Rᵥ₂) + oR = apply(o, Rᵥ₁ * Rᵥ₂) + e = v⃗[1] => v⃗[2] + singular_values! = Ref(ITensor()) + Rᵥ₁, Rᵥ₂, spec = factorize_svd( + oR, + unioninds(rᵥ₁, sᵥ₁); + ortho = "none", + tags = edge_tag(e), + singular_values!, + apply_kwargs..., + ) + callback(; singular_values = singular_values![], truncation_error = spec.truncerr) + Qᵥ₁ = contract([Qᵥ₁; dag.(inv_sqrt_envs_v1)]) + Qᵥ₂ = contract([Qᵥ₂; dag.(inv_sqrt_envs_v2)]) + ψᵥ₁ = Qᵥ₁ * Rᵥ₁ + ψᵥ₂ = Qᵥ₂ * Rᵥ₂ + return ψᵥ₁, ψᵥ₂ end function ITensors.apply( - o::Union{NamedEdge,ITensor}, - ψ::AbstractITensorNetwork; - envs=ITensor[], - normalize=false, - ortho=false, - nfullupdatesweeps=10, - print_fidelity_loss=false, - envisposdef=false, - callback=Returns(nothing), - variational_optimization_only=false, - symmetrize=false, - reduced=true, - apply_kwargs..., -) - ψ = copy(ψ) - v⃗ = neighbor_vertices(ψ, o) - if length(v⃗) == 1 - if ortho - ψ = tree_orthogonalize(ψ, v⃗[1]) - end - oψᵥ = apply(o, ψ[v⃗[1]]) - if normalize - oψᵥ ./= norm(oψᵥ) - end - setindex_preserve_graph!(ψ, oψᵥ, v⃗[1]) - elseif length(v⃗) == 2 - envs = Vector{ITensor}(envs) - is_product_env = iszero(ne(ITensorNetwork(envs))) - e = v⃗[1] => v⃗[2] - if !has_edge(ψ, e) - error("Vertices where the gates are being applied must be neighbors for now.") - end - if ortho - ψ = tree_orthogonalize(ψ, v⃗[1]) - end - if variational_optimization_only || !is_product_env - ψᵥ₁, ψᵥ₂ = full_update_bp( - o, - ψ, - v⃗; - envs, - nfullupdatesweeps, - print_fidelity_loss, - envisposdef, - callback, - symmetrize, + o::Union{NamedEdge, ITensor}, + ψ::AbstractITensorNetwork; + envs = ITensor[], + normalize = false, + ortho = false, + nfullupdatesweeps = 10, + print_fidelity_loss = false, + envisposdef = false, + callback = Returns(nothing), + variational_optimization_only = false, + symmetrize = false, + reduced = true, apply_kwargs..., - ) - else - if reduced - ψᵥ₁, ψᵥ₂ = simple_update_bp(o, ψ, v⃗; envs, callback, apply_kwargs...) - else - ψᵥ₁, ψᵥ₂ = simple_update_bp_full(o, ψ, v⃗; envs, callback, apply_kwargs...) - end - end - if normalize - ψᵥ₁ ./= norm(ψᵥ₁) - ψᵥ₂ ./= norm(ψᵥ₂) + ) + ψ = copy(ψ) + v⃗ = neighbor_vertices(ψ, o) + if length(v⃗) == 1 + if ortho + ψ = tree_orthogonalize(ψ, v⃗[1]) + end + oψᵥ = apply(o, ψ[v⃗[1]]) + if normalize + oψᵥ ./= norm(oψᵥ) + end + setindex_preserve_graph!(ψ, oψᵥ, v⃗[1]) + elseif length(v⃗) == 2 + envs = Vector{ITensor}(envs) + is_product_env = iszero(ne(ITensorNetwork(envs))) + e = v⃗[1] => v⃗[2] + if !has_edge(ψ, e) + error("Vertices where the gates are being applied must be neighbors for now.") + end + if ortho + ψ = tree_orthogonalize(ψ, v⃗[1]) + end + if variational_optimization_only || !is_product_env + ψᵥ₁, ψᵥ₂ = full_update_bp( + o, + ψ, + v⃗; + envs, + nfullupdatesweeps, + print_fidelity_loss, + envisposdef, + callback, + symmetrize, + apply_kwargs..., + ) + else + if reduced + ψᵥ₁, ψᵥ₂ = simple_update_bp(o, ψ, v⃗; envs, callback, apply_kwargs...) + else + ψᵥ₁, ψᵥ₂ = simple_update_bp_full(o, ψ, v⃗; envs, callback, apply_kwargs...) + end + end + if normalize + ψᵥ₁ ./= norm(ψᵥ₁) + ψᵥ₂ ./= norm(ψᵥ₂) + end + setindex_preserve_graph!(ψ, ψᵥ₁, v⃗[1]) + setindex_preserve_graph!(ψ, ψᵥ₂, v⃗[2]) + elseif length(v⃗) < 1 + error("Gate being applied does not share indices with tensor network.") + elseif length(v⃗) > 2 + error("Gates with more than 2 sites is not supported yet.") end - setindex_preserve_graph!(ψ, ψᵥ₁, v⃗[1]) - setindex_preserve_graph!(ψ, ψᵥ₂, v⃗[2]) - elseif length(v⃗) < 1 - error("Gate being applied does not share indices with tensor network.") - elseif length(v⃗) > 2 - error("Gates with more than 2 sites is not supported yet.") - end - return ψ + return ψ end function ITensors.apply( - o⃗::Union{Vector{NamedEdge},Vector{ITensor}}, - ψ::AbstractITensorNetwork; - normalize=false, - ortho=false, - apply_kwargs..., -) - o⃗ψ = ψ - for oᵢ in o⃗ - o⃗ψ = apply(oᵢ, o⃗ψ; normalize, ortho, apply_kwargs...) - end - return o⃗ψ + o⃗::Union{Vector{NamedEdge}, Vector{ITensor}}, + ψ::AbstractITensorNetwork; + normalize = false, + ortho = false, + apply_kwargs..., + ) + o⃗ψ = ψ + for oᵢ in o⃗ + o⃗ψ = apply(oᵢ, o⃗ψ; normalize, ortho, apply_kwargs...) + end + return o⃗ψ end function ITensors.apply( - o⃗::Scaled, - ψ::AbstractITensorNetwork; - cutoff=nothing, - normalize=false, - ortho=false, - apply_kwargs..., -) - return maybe_real(Ops.coefficient(o⃗)) * - apply(Ops.argument(o⃗), ψ; cutoff, maxdim, normalize, ortho, apply_kwargs...) + o⃗::Scaled, + ψ::AbstractITensorNetwork; + cutoff = nothing, + normalize = false, + ortho = false, + apply_kwargs..., + ) + return maybe_real(Ops.coefficient(o⃗)) * + apply(Ops.argument(o⃗), ψ; cutoff, maxdim, normalize, ortho, apply_kwargs...) end function ITensors.apply( - o⃗::Prod, ψ::AbstractITensorNetwork; normalize=false, ortho=false, apply_kwargs... -) - o⃗ψ = ψ - for oᵢ in o⃗ - o⃗ψ = apply(oᵢ, o⃗ψ; normalize, ortho, apply_kwargs...) - end - return o⃗ψ + o⃗::Prod, ψ::AbstractITensorNetwork; normalize = false, ortho = false, apply_kwargs... + ) + o⃗ψ = ψ + for oᵢ in o⃗ + o⃗ψ = apply(oᵢ, o⃗ψ; normalize, ortho, apply_kwargs...) + end + return o⃗ψ end function ITensors.apply( - o::Op, ψ::AbstractITensorNetwork; normalize=false, ortho=false, apply_kwargs... -) - return apply(ITensor(o, siteinds(ψ)), ψ; normalize, ortho, apply_kwargs...) + o::Op, ψ::AbstractITensorNetwork; normalize = false, ortho = false, apply_kwargs... + ) + return apply(ITensor(o, siteinds(ψ)), ψ; normalize, ortho, apply_kwargs...) end _gate_vertices(o::ITensor, ψ) = neighbor_vertices(ψ, o) _gate_vertices(o::AbstractEdge, ψ) = [src(o), dst(o)] function _contract_gate(o::ITensor, ψv1, Λ, ψv2) - indsᵥ₁ = noprime(noncommoninds(ψv1, Λ)) - Qᵥ₁, Rᵥ₁ = qr(ψv1, setdiff(uniqueinds(indsᵥ₁, ψv2), commoninds(indsᵥ₁, o))) - Qᵥ₂, Rᵥ₂ = qr(ψv2, setdiff(uniqueinds(ψv2, indsᵥ₁), commoninds(ψv2, o))) - theta = noprime(noprime(Rᵥ₁ * Λ) * Rᵥ₂ * o) - return Qᵥ₁, Rᵥ₁, Qᵥ₂, Rᵥ₂, theta + indsᵥ₁ = noprime(noncommoninds(ψv1, Λ)) + Qᵥ₁, Rᵥ₁ = qr(ψv1, setdiff(uniqueinds(indsᵥ₁, ψv2), commoninds(indsᵥ₁, o))) + Qᵥ₂, Rᵥ₂ = qr(ψv2, setdiff(uniqueinds(ψv2, indsᵥ₁), commoninds(ψv2, o))) + theta = noprime(noprime(Rᵥ₁ * Λ) * Rᵥ₂ * o) + return Qᵥ₁, Rᵥ₁, Qᵥ₂, Rᵥ₂, theta end function _contract_gate(o::AbstractEdge, ψv1, Λ, ψv2) - indsᵥ₁ = noprime(noncommoninds(ψv1, Λ)) - Qᵥ₁, Rᵥ₁ = qr(ψv1, uniqueinds(indsᵥ₁, ψv2)) - Qᵥ₂, Rᵥ₂ = qr(ψv2, uniqueinds(ψv2, indsᵥ₁)) - theta = noprime(Rᵥ₁ * Λ) * Rᵥ₂ - return Qᵥ₁, Rᵥ₁, Qᵥ₂, Rᵥ₂, theta + indsᵥ₁ = noprime(noncommoninds(ψv1, Λ)) + Qᵥ₁, Rᵥ₁ = qr(ψv1, uniqueinds(indsᵥ₁, ψv2)) + Qᵥ₂, Rᵥ₂ = qr(ψv2, uniqueinds(ψv2, indsᵥ₁)) + theta = noprime(Rᵥ₁ * Λ) * Rᵥ₂ + return Qᵥ₁, Rᵥ₁, Qᵥ₂, Rᵥ₂, theta end #In the future we will try to unify this into apply() above but currently leave it mostly as a separate function """Apply() function for an ITN in the Vidal Gauge. Hence the bond tensors are required. Gate does not necessarily need to be passed. Can supply an edge to do an identity update instead. Uses Simple Update procedure assuming gate is two-site""" function ITensors.apply( - o::Union{NamedEdge,ITensor}, ψ::VidalITensorNetwork; normalize=false, apply_kwargs... -) - updated_ψ = copy(site_tensors(ψ)) - updated_bond_tensors = copy(bond_tensors(ψ)) - v⃗ = _gate_vertices(o, ψ) - if length(v⃗) == 2 - e = NamedEdge(v⃗[1] => v⃗[2]) - ψv1, ψv2 = ψ[src(e)], ψ[dst(e)] - e_ind = commonind(ψv1, ψv2) - - for vn in neighbors(ψ, src(e)) - if (vn != dst(e)) - ψv1 = noprime(ψv1 * bond_tensor(ψ, vn => src(e))) - end - end - - for vn in neighbors(ψ, dst(e)) - if (vn != src(e)) - ψv2 = noprime(ψv2 * bond_tensor(ψ, vn => dst(e))) - end - end - - Qᵥ₁, Rᵥ₁, Qᵥ₂, Rᵥ₂, theta = _contract_gate(o, ψv1, bond_tensor(ψ, e), ψv2) - - U, S, V = ITensors.svd( - theta, - uniqueinds(Rᵥ₁, Rᵥ₂); - lefttags=ITensorNetworks.edge_tag(e), - righttags=ITensorNetworks.edge_tag(e), - apply_kwargs..., + o::Union{NamedEdge, ITensor}, ψ::VidalITensorNetwork; normalize = false, apply_kwargs... ) + updated_ψ = copy(site_tensors(ψ)) + updated_bond_tensors = copy(bond_tensors(ψ)) + v⃗ = _gate_vertices(o, ψ) + if length(v⃗) == 2 + e = NamedEdge(v⃗[1] => v⃗[2]) + ψv1, ψv2 = ψ[src(e)], ψ[dst(e)] + e_ind = commonind(ψv1, ψv2) + + for vn in neighbors(ψ, src(e)) + if (vn != dst(e)) + ψv1 = noprime(ψv1 * bond_tensor(ψ, vn => src(e))) + end + end + + for vn in neighbors(ψ, dst(e)) + if (vn != src(e)) + ψv2 = noprime(ψv2 * bond_tensor(ψ, vn => dst(e))) + end + end + + Qᵥ₁, Rᵥ₁, Qᵥ₂, Rᵥ₂, theta = _contract_gate(o, ψv1, bond_tensor(ψ, e), ψv2) + + U, S, V = ITensors.svd( + theta, + uniqueinds(Rᵥ₁, Rᵥ₂); + lefttags = ITensorNetworks.edge_tag(e), + righttags = ITensorNetworks.edge_tag(e), + apply_kwargs..., + ) + + ind_to_replace = commonind(V, S) + ind_to_replace_with = commonind(U, S) + S = replaceind(S, ind_to_replace => ind_to_replace_with') + V = replaceind(V, ind_to_replace => ind_to_replace_with) + + ψv1, updated_bond_tensors[e], ψv2 = U * Qᵥ₁, S, V * Qᵥ₂ + + for vn in neighbors(ψ, src(e)) + if (vn != dst(e)) + ψv1 = noprime(ψv1 * ITensorsExtensions.inv_diag(bond_tensor(ψ, vn => src(e)))) + end + end + + for vn in neighbors(ψ, dst(e)) + if (vn != src(e)) + ψv2 = noprime(ψv2 * ITensorsExtensions.inv_diag(bond_tensor(ψ, vn => dst(e)))) + end + end + + if normalize + ψv1 /= norm(ψv1) + ψv2 /= norm(ψv2) + updated_bond_tensors[e] /= norm(updated_bond_tensors[e]) + end + + setindex_preserve_graph!(updated_ψ, ψv1, src(e)) + setindex_preserve_graph!(updated_ψ, ψv2, dst(e)) + + return VidalITensorNetwork(updated_ψ, updated_bond_tensors) - ind_to_replace = commonind(V, S) - ind_to_replace_with = commonind(U, S) - S = replaceind(S, ind_to_replace => ind_to_replace_with') - V = replaceind(V, ind_to_replace => ind_to_replace_with) - - ψv1, updated_bond_tensors[e], ψv2 = U * Qᵥ₁, S, V * Qᵥ₂ - - for vn in neighbors(ψ, src(e)) - if (vn != dst(e)) - ψv1 = noprime(ψv1 * ITensorsExtensions.inv_diag(bond_tensor(ψ, vn => src(e)))) - end - end - - for vn in neighbors(ψ, dst(e)) - if (vn != src(e)) - ψv2 = noprime(ψv2 * ITensorsExtensions.inv_diag(bond_tensor(ψ, vn => dst(e)))) - end - end - - if normalize - ψv1 /= norm(ψv1) - ψv2 /= norm(ψv2) - updated_bond_tensors[e] /= norm(updated_bond_tensors[e]) + else + updated_ψ = apply(o, updated_ψ; normalize) + return VidalITensorNetwork(updated_ψ, updated_bond_tensors) end - - setindex_preserve_graph!(updated_ψ, ψv1, src(e)) - setindex_preserve_graph!(updated_ψ, ψv2, dst(e)) - - return VidalITensorNetwork(updated_ψ, updated_bond_tensors) - - else - updated_ψ = apply(o, updated_ψ; normalize) - return VidalITensorNetwork(updated_ψ, updated_bond_tensors) - end end ### Full Update Routines ### """Calculate the overlap of the gate acting on the previous p and q versus the new p and q in the presence of environments. This is the cost function that optimise_p_q will minimise""" function fidelity( - envs::Vector{ITensor}, - p_cur::ITensor, - q_cur::ITensor, - p_prev::ITensor, - q_prev::ITensor, - gate::ITensor, -) - p_sind, q_sind = commonind(p_cur, gate), commonind(q_cur, gate) - p_sind_sim, q_sind_sim = sim(p_sind), sim(q_sind) - gate_sq = - gate * replaceinds(dag(gate), Index[p_sind, q_sind], Index[p_sind_sim, q_sind_sim]) - term1_tns = vcat( - [ - p_prev, - q_prev, - replaceind(prime(dag(p_prev)), prime(p_sind), p_sind_sim), - replaceind(prime(dag(q_prev)), prime(q_sind), q_sind_sim), - gate_sq, - ], - envs, - ) - sequence = contraction_sequence(term1_tns; alg="optimal") - term1 = ITensors.contract(term1_tns; sequence) - - term2_tns = vcat( - [ - p_cur, - q_cur, - replaceind(prime(dag(p_cur)), prime(p_sind), p_sind), - replaceind(prime(dag(q_cur)), prime(q_sind), q_sind), - ], - envs, - ) - sequence = contraction_sequence(term2_tns; alg="optimal") - term2 = ITensors.contract(term2_tns; sequence) - term3_tns = vcat([p_prev, q_prev, prime(dag(p_cur)), prime(dag(q_cur)), gate], envs) - sequence = contraction_sequence(term3_tns; alg="optimal") - term3 = ITensors.contract(term3_tns; sequence) - - f = term3[] / sqrt(term1[] * term2[]) - return f * conj(f) + envs::Vector{ITensor}, + p_cur::ITensor, + q_cur::ITensor, + p_prev::ITensor, + q_prev::ITensor, + gate::ITensor, + ) + p_sind, q_sind = commonind(p_cur, gate), commonind(q_cur, gate) + p_sind_sim, q_sind_sim = sim(p_sind), sim(q_sind) + gate_sq = + gate * replaceinds(dag(gate), Index[p_sind, q_sind], Index[p_sind_sim, q_sind_sim]) + term1_tns = vcat( + [ + p_prev, + q_prev, + replaceind(prime(dag(p_prev)), prime(p_sind), p_sind_sim), + replaceind(prime(dag(q_prev)), prime(q_sind), q_sind_sim), + gate_sq, + ], + envs, + ) + sequence = contraction_sequence(term1_tns; alg = "optimal") + term1 = ITensors.contract(term1_tns; sequence) + + term2_tns = vcat( + [ + p_cur, + q_cur, + replaceind(prime(dag(p_cur)), prime(p_sind), p_sind), + replaceind(prime(dag(q_cur)), prime(q_sind), q_sind), + ], + envs, + ) + sequence = contraction_sequence(term2_tns; alg = "optimal") + term2 = ITensors.contract(term2_tns; sequence) + term3_tns = vcat([p_prev, q_prev, prime(dag(p_cur)), prime(dag(q_cur)), gate], envs) + sequence = contraction_sequence(term3_tns; alg = "optimal") + term3 = ITensors.contract(term3_tns; sequence) + + f = term3[] / sqrt(term1[] * term2[]) + return f * conj(f) end """Do Full Update Sweeping, Optimising the tensors p and q in the presence of the environments envs, Specifically this functions find the p_cur and q_cur which optimise envs*gate*p*q*dag(prime(p_cur))*dag(prime(q_cur))""" function optimise_p_q( - p::ITensor, - q::ITensor, - envs::Vector{ITensor}, - o::ITensor; - nfullupdatesweeps=10, - print_fidelity_loss=false, - envisposdef=true, - apply_kwargs..., -) - p_cur, q_cur = factorize( - apply(o, p * q), inds(p); tags=tags(commonind(p, q)), apply_kwargs... - ) - - fstart = print_fidelity_loss ? fidelity(envs, p_cur, q_cur, p, q, o) : 0 - - qs_ind = setdiff(inds(q_cur), collect(Iterators.flatten(inds.(vcat(envs, p_cur))))) - ps_ind = setdiff(inds(p_cur), collect(Iterators.flatten(inds.(vcat(envs, q_cur))))) - - function b(p::ITensor, q::ITensor, o::ITensor, envs::Vector{ITensor}, r::ITensor) - ts = vcat(ITensor[p, q, o, dag(prime(r))], envs) - sequence = contraction_sequence(ts; alg="optimal") - return noprime(ITensors.contract(ts; sequence)) - end - - function M_p(envs::Vector{ITensor}, p_q_tensor::ITensor, s_ind, apply_tensor::ITensor) - ts = vcat( - ITensor[ - p_q_tensor, replaceinds(prime(dag(p_q_tensor)), prime(s_ind), s_ind), apply_tensor - ], - envs, + p::ITensor, + q::ITensor, + envs::Vector{ITensor}, + o::ITensor; + nfullupdatesweeps = 10, + print_fidelity_loss = false, + envisposdef = true, + apply_kwargs..., ) - sequence = contraction_sequence(ts; alg="optimal") - return noprime(ITensors.contract(ts; sequence)) - end - for i in 1:nfullupdatesweeps - b_vec = b(p, q, o, envs, q_cur) - M_p_partial = partial(M_p, envs, q_cur, qs_ind) - - p_cur, info = linsolve( - M_p_partial, b_vec, p_cur; isposdef=envisposdef, ishermitian=false + p_cur, q_cur = factorize( + apply(o, p * q), inds(p); tags = tags(commonind(p, q)), apply_kwargs... ) - b_tilde_vec = b(p, q, o, envs, p_cur) - M_p_tilde_partial = partial(M_p, envs, p_cur, ps_ind) + fstart = print_fidelity_loss ? fidelity(envs, p_cur, q_cur, p, q, o) : 0 - q_cur, info = linsolve( - M_p_tilde_partial, b_tilde_vec, q_cur; isposdef=envisposdef, ishermitian=false - ) - end + qs_ind = setdiff(inds(q_cur), collect(Iterators.flatten(inds.(vcat(envs, p_cur))))) + ps_ind = setdiff(inds(p_cur), collect(Iterators.flatten(inds.(vcat(envs, q_cur))))) - fend = print_fidelity_loss ? fidelity(envs, p_cur, q_cur, p, q, o) : 0 + function b(p::ITensor, q::ITensor, o::ITensor, envs::Vector{ITensor}, r::ITensor) + ts = vcat(ITensor[p, q, o, dag(prime(r))], envs) + sequence = contraction_sequence(ts; alg = "optimal") + return noprime(ITensors.contract(ts; sequence)) + end - diff = real(fend - fstart) - if print_fidelity_loss && diff < -eps(diff) && nfullupdatesweeps >= 1 - println( - "Warning: Krylov Solver Didn't Find a Better Solution by Sweeping. Something might be amiss.", - ) - end + function M_p(envs::Vector{ITensor}, p_q_tensor::ITensor, s_ind, apply_tensor::ITensor) + ts = vcat( + ITensor[ + p_q_tensor, replaceinds(prime(dag(p_q_tensor)), prime(s_ind), s_ind), apply_tensor, + ], + envs, + ) + sequence = contraction_sequence(ts; alg = "optimal") + return noprime(ITensors.contract(ts; sequence)) + end + for i in 1:nfullupdatesweeps + b_vec = b(p, q, o, envs, q_cur) + M_p_partial = partial(M_p, envs, q_cur, qs_ind) + + p_cur, info = linsolve( + M_p_partial, b_vec, p_cur; isposdef = envisposdef, ishermitian = false + ) + + b_tilde_vec = b(p, q, o, envs, p_cur) + M_p_tilde_partial = partial(M_p, envs, p_cur, ps_ind) + + q_cur, info = linsolve( + M_p_tilde_partial, b_tilde_vec, q_cur; isposdef = envisposdef, ishermitian = false + ) + end + + fend = print_fidelity_loss ? fidelity(envs, p_cur, q_cur, p, q, o) : 0 + + diff = real(fend - fstart) + if print_fidelity_loss && diff < -eps(diff) && nfullupdatesweeps >= 1 + println( + "Warning: Krylov Solver Didn't Find a Better Solution by Sweeping. Something might be amiss.", + ) + end - return p_cur, q_cur + return p_cur, q_cur end partial = (f, a...; c...) -> (b...) -> f(a..., b...; c...) diff --git a/src/caches/abstractbeliefpropagationcache.jl b/src/caches/abstractbeliefpropagationcache.jl index b29d2996..6421b31d 100644 --- a/src/caches/abstractbeliefpropagationcache.jl +++ b/src/caches/abstractbeliefpropagationcache.jl @@ -4,73 +4,73 @@ using SplitApplyCombine: group using LinearAlgebra: diag, dot using ITensors: dir using NamedGraphs.PartitionedGraphs: - PartitionedGraphs, - PartitionedGraph, - PartitionVertex, - boundary_partitionedges, - partitionvertices, - partitionedges, - unpartitioned_graph + PartitionedGraphs, + PartitionedGraph, + PartitionVertex, + boundary_partitionedges, + partitionvertices, + partitionedges, + unpartitioned_graph using SimpleTraits: SimpleTraits, Not, @traitfn using NamedGraphs.SimilarType: SimilarType using NDTensors: NDTensors -abstract type AbstractBeliefPropagationCache{V,PV} <: AbstractITensorNetwork{V} end +abstract type AbstractBeliefPropagationCache{V, PV} <: AbstractITensorNetwork{V} end function SimilarType.similar_type(bpc::AbstractBeliefPropagationCache) - return typeof(tensornetwork(bpc)) + return typeof(tensornetwork(bpc)) end function data_graph_type(bpc::AbstractBeliefPropagationCache) - return data_graph_type(tensornetwork(bpc)) + return data_graph_type(tensornetwork(bpc)) end data_graph(bpc::AbstractBeliefPropagationCache) = data_graph(tensornetwork(bpc)) #TODO: Take `dot` without precontracting the messages to allow scaling to more complex messages function message_diff(message_a::Vector{ITensor}, message_b::Vector{ITensor}) - lhs, rhs = contract(message_a), contract(message_b) - f = abs2(dot(lhs / norm(lhs), rhs / norm(rhs))) - return 1 - f + lhs, rhs = contract(message_a), contract(message_b) + f = abs2(dot(lhs / norm(lhs), rhs / norm(rhs))) + return 1 - f end function default_message(datatype::Type{<:AbstractArray}, inds_e) - return [adapt(datatype, denseblocks(delta(i))) for i in inds_e] + return [adapt(datatype, denseblocks(delta(i))) for i in inds_e] end function default_message(elt::Type{<:Number}, inds_e) - return default_message(Vector{elt}, inds_e) + return default_message(Vector{elt}, inds_e) end default_messages(ptn::PartitionedGraph) = Dictionary() @traitfn default_bp_maxiter(g::::(!IsDirected)) = is_tree(g) ? 1 : nothing @traitfn function default_bp_maxiter(g::::IsDirected) - return default_bp_maxiter(undirected_graph(underlying_graph(g))) + return default_bp_maxiter(undirected_graph(underlying_graph(g))) end default_partitioned_vertices(ψ::AbstractITensorNetwork) = group(v -> v, vertices(ψ)) function Base.setindex!(bpc::AbstractBeliefPropagationCache, factor::ITensor, vertex) - return not_implemented() + return not_implemented() end partitioned_tensornetwork(bpc::AbstractBeliefPropagationCache) = not_implemented() messages(bpc::AbstractBeliefPropagationCache) = not_implemented() function default_message( - bpc::AbstractBeliefPropagationCache, edge::PartitionEdge; kwargs... -) - return not_implemented() + bpc::AbstractBeliefPropagationCache, edge::PartitionEdge; kwargs... + ) + return not_implemented() end default_update_alg(bpc::AbstractBeliefPropagationCache) = not_implemented() default_message_update_alg(bpc::AbstractBeliefPropagationCache) = not_implemented() Base.copy(bpc::AbstractBeliefPropagationCache) = not_implemented() default_bp_maxiter(alg::Algorithm, bpc::AbstractBeliefPropagationCache) = not_implemented() function default_edge_sequence(alg::Algorithm, bpc::AbstractBeliefPropagationCache) - return not_implemented() + return not_implemented() end function environment(bpc::AbstractBeliefPropagationCache, verts::Vector; kwargs...) - return not_implemented() + return not_implemented() end function region_scalar(bpc::AbstractBeliefPropagationCache, pv::PartitionVertex; kwargs...) - return not_implemented() + return not_implemented() end function region_scalar(bpc::AbstractBeliefPropagationCache, pe::PartitionEdge; kwargs...) - return not_implemented() + return not_implemented() end partitions(bpc::AbstractBeliefPropagationCache) = not_implemented() PartitionedGraphs.partitionedges(bpc::AbstractBeliefPropagationCache) = not_implemented() @@ -79,100 +79,100 @@ default_bp_edge_sequence(bpc::AbstractBeliefPropagationCache) = not_implemented( default_bp_maxiter(bpc::AbstractBeliefPropagationCache) = not_implemented() function tensornetwork(bpc::AbstractBeliefPropagationCache) - return unpartitioned_graph(partitioned_tensornetwork(bpc)) + return unpartitioned_graph(partitioned_tensornetwork(bpc)) end function factors(bpc::AbstractBeliefPropagationCache, verts::Vector) - return ITensor[bpc[v] for v in verts] + return ITensor[bpc[v] for v in verts] end function factors( - bpc::AbstractBeliefPropagationCache, partition_verts::Vector{<:PartitionVertex} -) - return factors(bpc, vertices(bpc, partition_verts)) + bpc::AbstractBeliefPropagationCache, partition_verts::Vector{<:PartitionVertex} + ) + return factors(bpc, vertices(bpc, partition_verts)) end function factors(bpc::AbstractBeliefPropagationCache, partition_vertex::PartitionVertex) - return factors(bpc, [partition_vertex]) + return factors(bpc, [partition_vertex]) end -function vertex_scalars(bpc::AbstractBeliefPropagationCache, pvs=partitions(bpc); kwargs...) - return map(pv -> region_scalar(bpc, pv; kwargs...), pvs) +function vertex_scalars(bpc::AbstractBeliefPropagationCache, pvs = partitions(bpc); kwargs...) + return map(pv -> region_scalar(bpc, pv; kwargs...), pvs) end function edge_scalars( - bpc::AbstractBeliefPropagationCache, pes=partitionedges(bpc); kwargs... -) - return map(pe -> region_scalar(bpc, pe; kwargs...), pes) + bpc::AbstractBeliefPropagationCache, pes = partitionedges(bpc); kwargs... + ) + return map(pe -> region_scalar(bpc, pe; kwargs...), pes) end function scalar_factors_quotient(bpc::AbstractBeliefPropagationCache) - return vertex_scalars(bpc), edge_scalars(bpc) + return vertex_scalars(bpc), edge_scalars(bpc) end function incoming_messages( - bpc::AbstractBeliefPropagationCache, - partition_vertices::Vector{<:PartitionVertex}; - ignore_edges=(), -) - bpes = boundary_partitionedges(bpc, partition_vertices; dir=:in) - ms = messages(bpc, setdiff(bpes, ignore_edges)) - return reduce(vcat, ms; init=ITensor[]) + bpc::AbstractBeliefPropagationCache, + partition_vertices::Vector{<:PartitionVertex}; + ignore_edges = (), + ) + bpes = boundary_partitionedges(bpc, partition_vertices; dir = :in) + ms = messages(bpc, setdiff(bpes, ignore_edges)) + return reduce(vcat, ms; init = ITensor[]) end function incoming_messages( - bpc::AbstractBeliefPropagationCache, partition_vertex::PartitionVertex; kwargs... -) - return incoming_messages(bpc, [partition_vertex]; kwargs...) + bpc::AbstractBeliefPropagationCache, partition_vertex::PartitionVertex; kwargs... + ) + return incoming_messages(bpc, [partition_vertex]; kwargs...) end #Adapt interface for changing device function map_messages( - f, bpc::AbstractBeliefPropagationCache, pes=collect(keys(messages(bpc))) -) - bpc = copy(bpc) - for pe in pes - set_message!(bpc, pe, f.(message(bpc, pe))) - end - return bpc -end -function map_factors(f, bpc::AbstractBeliefPropagationCache, vs=vertices(bpc)) - bpc = copy(bpc) - for v in vs - @preserve_graph bpc[v] = f(bpc[v]) - end - return bpc + f, bpc::AbstractBeliefPropagationCache, pes = collect(keys(messages(bpc))) + ) + bpc = copy(bpc) + for pe in pes + set_message!(bpc, pe, f.(message(bpc, pe))) + end + return bpc +end +function map_factors(f, bpc::AbstractBeliefPropagationCache, vs = vertices(bpc)) + bpc = copy(bpc) + for v in vs + @preserve_graph bpc[v] = f(bpc[v]) + end + return bpc end function adapt_messages(to, bpc::AbstractBeliefPropagationCache, args...) - return map_messages(adapt(to), bpc, args...) + return map_messages(adapt(to), bpc, args...) end function adapt_factors(to, bpc::AbstractBeliefPropagationCache, args...) - return map_factors(adapt(to), bpc, args...) + return map_factors(adapt(to), bpc, args...) end function Adapt.adapt_structure(to, bpc::AbstractBeliefPropagationCache) - bpc = adapt_messages(to, bpc) - bpc = adapt_factors(to, bpc) - return bpc + bpc = adapt_messages(to, bpc) + bpc = adapt_factors(to, bpc) + return bpc end #Forward from partitioned graph for f in [ - :(PartitionedGraphs.partitionedge), - :(PartitionedGraphs.partitionvertices), - :(PartitionedGraphs.partitions_graph), - :(PartitionedGraphs.vertices), - :(PartitionedGraphs.boundary_partitionedges), -] - @eval begin - function $f(bpc::AbstractBeliefPropagationCache, args...; kwargs...) - return $f(partitioned_tensornetwork(bpc), args...; kwargs...) + :(PartitionedGraphs.partitionedge), + :(PartitionedGraphs.partitionvertices), + :(PartitionedGraphs.partitions_graph), + :(PartitionedGraphs.vertices), + :(PartitionedGraphs.boundary_partitionedges), + ] + @eval begin + function $f(bpc::AbstractBeliefPropagationCache, args...; kwargs...) + return $f(partitioned_tensornetwork(bpc), args...; kwargs...) + end end - end end function linkinds(bpc::AbstractBeliefPropagationCache, pe::PartitionEdge) - return linkinds(partitioned_tensornetwork(bpc), pe) + return linkinds(partitioned_tensornetwork(bpc), pe) end NDTensors.scalartype(bpc::AbstractBeliefPropagationCache) = scalartype(tensornetwork(bpc)) @@ -181,133 +181,133 @@ NDTensors.scalartype(bpc::AbstractBeliefPropagationCache) = scalartype(tensornet Update the tensornetwork inside the cache out-of-place """ function update_factors(bpc::AbstractBeliefPropagationCache, factors) - bpc = copy(bpc) - for vertex in eachindex(factors) - # TODO: Add a check that this preserves the graph structure. - setindex_preserve_graph!(bpc, factors[vertex], vertex) - end - return bpc + bpc = copy(bpc) + for vertex in eachindex(factors) + # TODO: Add a check that this preserves the graph structure. + setindex_preserve_graph!(bpc, factors[vertex], vertex) + end + return bpc end function update_factor(bpc, vertex, factor) - bpc = copy(bpc) - setindex_preserve_graph!(bpc, factor, vertex) - return bpc + bpc = copy(bpc) + setindex_preserve_graph!(bpc, factor, vertex) + return bpc end function message(bpc::AbstractBeliefPropagationCache, edge::PartitionEdge; kwargs...) - mts = messages(bpc) - return get(() -> default_message(bpc, edge; kwargs...), mts, edge) + mts = messages(bpc) + return get(() -> default_message(bpc, edge; kwargs...), mts, edge) end function messages(bpc::AbstractBeliefPropagationCache, edges; kwargs...) - return map(edge -> message(bpc, edge; kwargs...), edges) + return map(edge -> message(bpc, edge; kwargs...), edges) end function set_messages!(bpc::AbstractBeliefPropagationCache, partitionedges_messages) - ms = messages(bpc) - for pe in eachindex(partitionedges_messages) - # TODO: Add a check that this preserves the graph structure. - set!(ms, pe, partitionedges_messages[pe]) - end - return bpc + ms = messages(bpc) + for pe in eachindex(partitionedges_messages) + # TODO: Add a check that this preserves the graph structure. + set!(ms, pe, partitionedges_messages[pe]) + end + return bpc end function set_message!(bpc::AbstractBeliefPropagationCache, pe::PartitionEdge, message) - ms = messages(bpc) - set!(ms, pe, message) - return bpc + ms = messages(bpc) + set!(ms, pe, message) + return bpc end function set_messages(bpc::AbstractBeliefPropagationCache, partitionedges_messages) - bpc = copy(bpc) - return set_messages!(bpc, partitionedges_messages) + bpc = copy(bpc) + return set_messages!(bpc, partitionedges_messages) end function set_message(bpc::AbstractBeliefPropagationCache, pe::PartitionEdge, message) - bpc = copy(bpc) - return set_message!(bpc, pe, message) + bpc = copy(bpc) + return set_message!(bpc, pe, message) end function delete_messages!( - bpc::AbstractBeliefPropagationCache, pes::Vector{<:PartitionEdge}=keys(messages(bpc)) -) - ms = messages(bpc) - for pe in pes - delete!(ms, pe) - end - return bpc + bpc::AbstractBeliefPropagationCache, pes::Vector{<:PartitionEdge} = keys(messages(bpc)) + ) + ms = messages(bpc) + for pe in pes + delete!(ms, pe) + end + return bpc end function delete_message!(bpc::AbstractBeliefPropagationCache, pe::PartitionEdge) - return delete_messages!(bpc, [pe]) + return delete_messages!(bpc, [pe]) end function delete_messages( - bpc::AbstractBeliefPropagationCache, pes::Vector{<:PartitionEdge}=keys(messages(bpc)) -) - bpc = copy(bpc) - return delete_messages!(bpc, pes) + bpc::AbstractBeliefPropagationCache, pes::Vector{<:PartitionEdge} = keys(messages(bpc)) + ) + bpc = copy(bpc) + return delete_messages!(bpc, pes) end function delete_message(bpc::AbstractBeliefPropagationCache, pe::PartitionEdge) - return delete_messages(bpc, [pe]) + return delete_messages(bpc, [pe]) end function updated_message( - alg::Algorithm"contract", bpc::AbstractBeliefPropagationCache, edge::PartitionEdge -) - vertex = src(edge) - incoming_ms = incoming_messages(bpc, vertex; ignore_edges=PartitionEdge[reverse(edge)]) - state = factors(bpc, vertex) - contract_list = ITensor[incoming_ms; state] - sequence = contraction_sequence(contract_list; alg=alg.kwargs.sequence_alg) - updated_messages = contract(contract_list; sequence) - message_norm = norm(updated_messages) - if alg.kwargs.normalize && !iszero(message_norm) - updated_messages /= message_norm - end - return ITensor[updated_messages] + alg::Algorithm"contract", bpc::AbstractBeliefPropagationCache, edge::PartitionEdge + ) + vertex = src(edge) + incoming_ms = incoming_messages(bpc, vertex; ignore_edges = PartitionEdge[reverse(edge)]) + state = factors(bpc, vertex) + contract_list = ITensor[incoming_ms; state] + sequence = contraction_sequence(contract_list; alg = alg.kwargs.sequence_alg) + updated_messages = contract(contract_list; sequence) + message_norm = norm(updated_messages) + if alg.kwargs.normalize && !iszero(message_norm) + updated_messages /= message_norm + end + return ITensor[updated_messages] end function updated_message( - alg::Algorithm"adapt_update", bpc::AbstractBeliefPropagationCache, edge::PartitionEdge -) - incoming_pes = setdiff( - boundary_partitionedges(bpc, [src(edge)]; dir=:in), [reverse(edge)] - ) - adapted_bpc = adapt_messages(alg.kwargs.adapt, bpc, incoming_pes) - adapted_bpc = adapt_factors(alg.kwargs.adapt, bpc, vertices(bpc, src(edge))) - updated_messages = updated_message(alg.kwargs.alg, adapted_bpc, edge) - dtype = mapreduce(datatype, promote_type, message(bpc, edge)) - return map(adapt(dtype), updated_messages) + alg::Algorithm"adapt_update", bpc::AbstractBeliefPropagationCache, edge::PartitionEdge + ) + incoming_pes = setdiff( + boundary_partitionedges(bpc, [src(edge)]; dir = :in), [reverse(edge)] + ) + adapted_bpc = adapt_messages(alg.kwargs.adapt, bpc, incoming_pes) + adapted_bpc = adapt_factors(alg.kwargs.adapt, bpc, vertices(bpc, src(edge))) + updated_messages = updated_message(alg.kwargs.alg, adapted_bpc, edge) + dtype = mapreduce(datatype, promote_type, message(bpc, edge)) + return map(adapt(dtype), updated_messages) end function updated_message( - bpc::AbstractBeliefPropagationCache, - edge::PartitionEdge; - alg=default_message_update_alg(bpc), - kwargs..., -) - return updated_message(set_default_kwargs(Algorithm(alg; kwargs...)), bpc, edge) + bpc::AbstractBeliefPropagationCache, + edge::PartitionEdge; + alg = default_message_update_alg(bpc), + kwargs..., + ) + return updated_message(set_default_kwargs(Algorithm(alg; kwargs...)), bpc, edge) end function update_message( - message_update_alg::Algorithm, bpc::AbstractBeliefPropagationCache, edge::PartitionEdge -) - return set_message(bpc, edge, updated_message(message_update_alg, bpc, edge)) + message_update_alg::Algorithm, bpc::AbstractBeliefPropagationCache, edge::PartitionEdge + ) + return set_message(bpc, edge, updated_message(message_update_alg, bpc, edge)) end """ Do a sequential update of the message tensors on `edges` """ function update_iteration( - alg::Algorithm"bp", - bpc::AbstractBeliefPropagationCache, - edges::Vector; - (update_diff!)=nothing, -) - bpc = copy(bpc) - for e in edges - prev_message = !isnothing(update_diff!) ? message(bpc, e) : nothing - bpc = update_message(alg.kwargs.message_update_alg, bpc, e) - if !isnothing(update_diff!) - update_diff![] += message_diff(message(bpc, e), prev_message) + alg::Algorithm"bp", + bpc::AbstractBeliefPropagationCache, + edges::Vector; + (update_diff!) = nothing, + ) + bpc = copy(bpc) + for e in edges + prev_message = !isnothing(update_diff!) ? message(bpc, e) : nothing + bpc = update_message(alg.kwargs.message_update_alg, bpc, e) + if !isnothing(update_diff!) + update_diff![] += message_diff(message(bpc, e), prev_message) + end end - end - return bpc + return bpc end """ @@ -316,114 +316,114 @@ Currently we send the full message tensor data struct to update for each edge_gr mts relevant to that group. """ function update_iteration( - alg::Algorithm"bp", - bpc::AbstractBeliefPropagationCache, - edge_groups::Vector{<:Vector{<:PartitionEdge}}; - (update_diff!)=nothing, -) - new_mts = empty(messages(bpc)) - for edges in edge_groups - bpc_t = update_iteration(alg.kwargs.message_update_alg, bpc, edges; (update_diff!)) - for e in edges - set!(new_mts, e, message(bpc_t, e)) + alg::Algorithm"bp", + bpc::AbstractBeliefPropagationCache, + edge_groups::Vector{<:Vector{<:PartitionEdge}}; + (update_diff!) = nothing, + ) + new_mts = empty(messages(bpc)) + for edges in edge_groups + bpc_t = update_iteration(alg.kwargs.message_update_alg, bpc, edges; (update_diff!)) + for e in edges + set!(new_mts, e, message(bpc_t, e)) + end end - end - return set_messages(bpc, new_mts) + return set_messages(bpc, new_mts) end """ More generic interface for update, with default params """ function update(alg::Algorithm"bp", bpc::AbstractBeliefPropagationCache) - compute_error = !isnothing(alg.kwargs.tol) - if isnothing(alg.kwargs.maxiter) - error("You need to specify a number of iterations for BP!") - end - for i in 1:alg.kwargs.maxiter - diff = compute_error ? Ref(0.0) : nothing - bpc = update_iteration(alg, bpc, alg.kwargs.edge_sequence; (update_diff!)=diff) - if compute_error && (diff.x / length(alg.kwargs.edge_sequence)) <= alg.kwargs.tol - if alg.kwargs.verbose - println("BP converged to desired precision after $i iterations.") - end - break + compute_error = !isnothing(alg.kwargs.tol) + if isnothing(alg.kwargs.maxiter) + error("You need to specify a number of iterations for BP!") end - end - return bpc + for i in 1:alg.kwargs.maxiter + diff = compute_error ? Ref(0.0) : nothing + bpc = update_iteration(alg, bpc, alg.kwargs.edge_sequence; (update_diff!) = diff) + if compute_error && (diff.x / length(alg.kwargs.edge_sequence)) <= alg.kwargs.tol + if alg.kwargs.verbose + println("BP converged to desired precision after $i iterations.") + end + break + end + end + return bpc end -function update(bpc::AbstractBeliefPropagationCache; alg=default_update_alg(bpc), kwargs...) - return update(set_default_kwargs(Algorithm(alg; kwargs...), bpc), bpc) +function update(bpc::AbstractBeliefPropagationCache; alg = default_update_alg(bpc), kwargs...) + return update(set_default_kwargs(Algorithm(alg; kwargs...), bpc), bpc) end function rescale_messages( - bp_cache::AbstractBeliefPropagationCache, partitionedge::PartitionEdge -) - return rescale_messages(bp_cache, [partitionedge]) + bp_cache::AbstractBeliefPropagationCache, partitionedge::PartitionEdge + ) + return rescale_messages(bp_cache, [partitionedge]) end function rescale_messages(bp_cache::AbstractBeliefPropagationCache) - return rescale_messages(bp_cache, partitionedges(bp_cache)) + return rescale_messages(bp_cache, partitionedges(bp_cache)) end function rescale_partitions( - bpc::AbstractBeliefPropagationCache, - partitions::Vector; - verts::Vector=vertices(bpc, partitions), -) - bpc = copy(bpc) - tn = tensornetwork(bpc) - norms = map(v -> inv(norm(tn[v])), verts) - scale!(bpc, Dictionary(verts, norms)) - - vertices_weights = Dictionary() - for pv in partitions - pv_vs = filter(v -> v ∈ verts, vertices(bpc, pv)) - isempty(pv_vs) && continue - - vn = region_scalar(bpc, pv) - s = isreal(vn) ? sign(vn) : one(vn) - vn = s * vn^(-inv(oftype(vn, length(pv_vs)))) - set!(vertices_weights, first(pv_vs), s*vn) - for v in pv_vs[2:length(pv_vs)] - set!(vertices_weights, v, vn) + bpc::AbstractBeliefPropagationCache, + partitions::Vector; + verts::Vector = vertices(bpc, partitions), + ) + bpc = copy(bpc) + tn = tensornetwork(bpc) + norms = map(v -> inv(norm(tn[v])), verts) + scale!(bpc, Dictionary(verts, norms)) + + vertices_weights = Dictionary() + for pv in partitions + pv_vs = filter(v -> v ∈ verts, vertices(bpc, pv)) + isempty(pv_vs) && continue + + vn = region_scalar(bpc, pv) + s = isreal(vn) ? sign(vn) : one(vn) + vn = s * vn^(-inv(oftype(vn, length(pv_vs)))) + set!(vertices_weights, first(pv_vs), s * vn) + for v in pv_vs[2:length(pv_vs)] + set!(vertices_weights, v, vn) + end end - end - scale!(bpc, vertices_weights) + scale!(bpc, vertices_weights) - return bpc + return bpc end function rescale_partitions(bpc::AbstractBeliefPropagationCache, args...; kwargs...) - return rescale_partitions(bpc, collect(partitions(bpc)), args...; kwargs...) + return rescale_partitions(bpc, collect(partitions(bpc)), args...; kwargs...) end function rescale_partition( - bpc::AbstractBeliefPropagationCache, partition, args...; kwargs... -) - return rescale_partitions(bpc, [partition], args...; kwargs...) + bpc::AbstractBeliefPropagationCache, partition, args...; kwargs... + ) + return rescale_partitions(bpc, [partition], args...; kwargs...) end function rescale(bpc::AbstractBeliefPropagationCache, args...; kwargs...) - bpc = rescale_messages(bpc) - bpc = rescale_partitions(bpc, args...; kwargs...) - return bpc + bpc = rescale_messages(bpc) + bpc = rescale_partitions(bpc, args...; kwargs...) + return bpc end function logscalar(bpc::AbstractBeliefPropagationCache) - numerator_terms, denominator_terms = scalar_factors_quotient(bpc) - if any(t -> real(t) < 0, numerator_terms) - numerator_terms = complex.(numerator_terms) - end - if any(t -> real(t) < 0, denominator_terms) - denominator_terms = complex.(denominator_terms) - end + numerator_terms, denominator_terms = scalar_factors_quotient(bpc) + if any(t -> real(t) < 0, numerator_terms) + numerator_terms = complex.(numerator_terms) + end + if any(t -> real(t) < 0, denominator_terms) + denominator_terms = complex.(denominator_terms) + end - any(iszero, denominator_terms) && return -Inf - return sum(log.(numerator_terms)) - sum(log.((denominator_terms))) + any(iszero, denominator_terms) && return -Inf + return sum(log.(numerator_terms)) - sum(log.((denominator_terms))) end function ITensors.scalar(bpc::AbstractBeliefPropagationCache) - return exp(logscalar(bpc)) + return exp(logscalar(bpc)) end diff --git a/src/caches/beliefpropagationcache.jl b/src/caches/beliefpropagationcache.jl index 36d01461..a431b152 100644 --- a/src/caches/beliefpropagationcache.jl +++ b/src/caches/beliefpropagationcache.jl @@ -3,70 +3,70 @@ using SplitApplyCombine: group using LinearAlgebra: diag, dot using ITensors: dir using NamedGraphs.PartitionedGraphs: - AbstractPartitionedGraph, - PartitionedGraphs, - PartitionedGraph, - PartitionVertex, - boundary_partitionedges, - partitionvertices, - partitionedges, - partitioned_vertices, - partitions_graph, - unpartitioned_graph, - which_partition + AbstractPartitionedGraph, + PartitionedGraphs, + PartitionedGraph, + PartitionVertex, + boundary_partitionedges, + partitionvertices, + partitionedges, + partitioned_vertices, + partitions_graph, + unpartitioned_graph, + which_partition using SimpleTraits: SimpleTraits, Not, @traitfn using NDTensors: NDTensors, Algorithm function default_cache_construction_kwargs(alg::Algorithm"bp", ψ::AbstractITensorNetwork) - return (; partitioned_vertices=default_partitioned_vertices(ψ)) + return (; partitioned_vertices = default_partitioned_vertices(ψ)) end function default_cache_construction_kwargs(alg::Algorithm"bp", pg::PartitionedGraph) - return (;) + return (;) end -struct BeliefPropagationCache{V,PV,PTN<:AbstractPartitionedGraph{V,PV},MTS} <: - AbstractBeliefPropagationCache{V,PV} - partitioned_tensornetwork::PTN - messages::MTS +struct BeliefPropagationCache{V, PV, PTN <: AbstractPartitionedGraph{V, PV}, MTS} <: + AbstractBeliefPropagationCache{V, PV} + partitioned_tensornetwork::PTN + messages::MTS end #Constructors... -function BeliefPropagationCache(ptn::PartitionedGraph; messages=default_messages(ptn)) - return BeliefPropagationCache(ptn, messages) +function BeliefPropagationCache(ptn::PartitionedGraph; messages = default_messages(ptn)) + return BeliefPropagationCache(ptn, messages) end function BeliefPropagationCache(tn::AbstractITensorNetwork, partitioned_vertices; kwargs...) - ptn = PartitionedGraph(tn, partitioned_vertices) - return BeliefPropagationCache(ptn; kwargs...) + ptn = PartitionedGraph(tn, partitioned_vertices) + return BeliefPropagationCache(ptn; kwargs...) end function BeliefPropagationCache( - tn::AbstractITensorNetwork; - partitioned_vertices=default_partitioned_vertices(tn), - kwargs..., -) - return BeliefPropagationCache(tn, partitioned_vertices; kwargs...) + tn::AbstractITensorNetwork; + partitioned_vertices = default_partitioned_vertices(tn), + kwargs..., + ) + return BeliefPropagationCache(tn, partitioned_vertices; kwargs...) end function cache(alg::Algorithm"bp", tn; kwargs...) - return BeliefPropagationCache(tn; kwargs...) + return BeliefPropagationCache(tn; kwargs...) end function partitioned_tensornetwork(bp_cache::BeliefPropagationCache) - return bp_cache.partitioned_tensornetwork + return bp_cache.partitioned_tensornetwork end messages(bp_cache::BeliefPropagationCache) = bp_cache.messages function default_message(bp_cache::BeliefPropagationCache, edge::PartitionEdge) - return default_message(datatype(bp_cache), linkinds(bp_cache, edge)) + return default_message(datatype(bp_cache), linkinds(bp_cache, edge)) end function Base.copy(bp_cache::BeliefPropagationCache) - return BeliefPropagationCache( - copy(partitioned_tensornetwork(bp_cache)), copy(messages(bp_cache)) - ) + return BeliefPropagationCache( + copy(partitioned_tensornetwork(bp_cache)), copy(messages(bp_cache)) + ) end default_update_alg(bp_cache::BeliefPropagationCache) = "bp" @@ -74,77 +74,77 @@ default_message_update_alg(bp_cache::BeliefPropagationCache) = "contract" default_normalize(::Algorithm"contract") = true default_sequence_alg(::Algorithm"contract") = "optimal" function set_default_kwargs(alg::Algorithm"contract") - normalize = get(alg.kwargs, :normalize, default_normalize(alg)) - sequence_alg = get(alg.kwargs, :sequence_alg, default_sequence_alg(alg)) - return Algorithm("contract"; normalize, sequence_alg) + normalize = get(alg.kwargs, :normalize, default_normalize(alg)) + sequence_alg = get(alg.kwargs, :sequence_alg, default_sequence_alg(alg)) + return Algorithm("contract"; normalize, sequence_alg) end function set_default_kwargs(alg::Algorithm"adapt_update") - _alg = set_default_kwargs(get(alg.kwargs, :alg, Algorithm("contract"))) - return Algorithm("adapt_update"; adapt=alg.kwargs.adapt, alg=_alg) + _alg = set_default_kwargs(get(alg.kwargs, :alg, Algorithm("contract"))) + return Algorithm("adapt_update"; adapt = alg.kwargs.adapt, alg = _alg) end default_verbose(::Algorithm"bp") = false default_tol(::Algorithm"bp") = nothing function set_default_kwargs(alg::Algorithm"bp", bp_cache::BeliefPropagationCache) - verbose = get(alg.kwargs, :verbose, default_verbose(alg)) - maxiter = get(alg.kwargs, :maxiter, default_bp_maxiter(bp_cache)) - edge_sequence = get(alg.kwargs, :edge_sequence, default_bp_edge_sequence(bp_cache)) - tol = get(alg.kwargs, :tol, default_tol(alg)) - message_update_alg = set_default_kwargs( - get(alg.kwargs, :message_update_alg, Algorithm(default_message_update_alg(bp_cache))) - ) - return Algorithm("bp"; verbose, maxiter, edge_sequence, tol, message_update_alg) + verbose = get(alg.kwargs, :verbose, default_verbose(alg)) + maxiter = get(alg.kwargs, :maxiter, default_bp_maxiter(bp_cache)) + edge_sequence = get(alg.kwargs, :edge_sequence, default_bp_edge_sequence(bp_cache)) + tol = get(alg.kwargs, :tol, default_tol(alg)) + message_update_alg = set_default_kwargs( + get(alg.kwargs, :message_update_alg, Algorithm(default_message_update_alg(bp_cache))) + ) + return Algorithm("bp"; verbose, maxiter, edge_sequence, tol, message_update_alg) end function default_bp_maxiter(bp_cache::BeliefPropagationCache) - return default_bp_maxiter(partitions_graph(bp_cache)) + return default_bp_maxiter(partitions_graph(bp_cache)) end function default_bp_edge_sequence(bp_cache::BeliefPropagationCache) - return default_edge_sequence(partitioned_tensornetwork(bp_cache)) + return default_edge_sequence(partitioned_tensornetwork(bp_cache)) end Base.setindex!(bpc::BeliefPropagationCache, factor::ITensor, vertex) = not_implemented() partitions(bpc::BeliefPropagationCache) = partitionvertices(partitioned_tensornetwork(bpc)) function PartitionedGraphs.partitionedges(bpc::BeliefPropagationCache) - partitionedges(partitioned_tensornetwork(bpc)) + return partitionedges(partitioned_tensornetwork(bpc)) end function environment(bpc::BeliefPropagationCache, verts::Vector; kwargs...) - partition_verts = partitionvertices(bpc, verts) - messages = incoming_messages(bpc, partition_verts; kwargs...) - central_tensors = factors(bpc, setdiff(vertices(bpc, partition_verts), verts)) - return vcat(messages, central_tensors) + partition_verts = partitionvertices(bpc, verts) + messages = incoming_messages(bpc, partition_verts; kwargs...) + central_tensors = factors(bpc, setdiff(vertices(bpc, partition_verts), verts)) + return vcat(messages, central_tensors) end function region_scalar(bp_cache::BeliefPropagationCache, pv::PartitionVertex) - incoming_mts = incoming_messages(bp_cache, [pv]) - local_state = factors(bp_cache, pv) - ts = vcat(incoming_mts, local_state) - sequence = contraction_sequence(ts; alg="optimal") - return contract(ts; sequence)[] + incoming_mts = incoming_messages(bp_cache, [pv]) + local_state = factors(bp_cache, pv) + ts = vcat(incoming_mts, local_state) + sequence = contraction_sequence(ts; alg = "optimal") + return contract(ts; sequence)[] end function region_scalar(bp_cache::BeliefPropagationCache, pe::PartitionEdge) - ts = vcat(message(bp_cache, pe), message(bp_cache, reverse(pe))) - sequence = contraction_sequence(ts; alg="optimal") - return contract(ts; sequence)[] + ts = vcat(message(bp_cache, pe), message(bp_cache, reverse(pe))) + sequence = contraction_sequence(ts; alg = "optimal") + return contract(ts; sequence)[] end function rescale_messages(bp_cache::BeliefPropagationCache, pes::Vector{<:PartitionEdge}) - bp_cache = copy(bp_cache) - mts = messages(bp_cache) - for pe in pes - me, mer = normalize.(mts[pe]), normalize.(mts[reverse(pe)]) - set!(mts, pe, me) - set!(mts, reverse(pe), mer) - n = region_scalar(bp_cache, pe) - if isreal(n) - me[1] *= sign(n) - n *= sign(n) + bp_cache = copy(bp_cache) + mts = messages(bp_cache) + for pe in pes + me, mer = normalize.(mts[pe]), normalize.(mts[reverse(pe)]) + set!(mts, pe, me) + set!(mts, reverse(pe), mer) + n = region_scalar(bp_cache, pe) + if isreal(n) + me[1] *= sign(n) + n *= sign(n) + end + + sf = inv(sqrt(n))^inv(oftype(n, length(me))) + set!(mts, pe, sf .* me) + set!(mts, reverse(pe), sf .* mer) end - - sf = inv(sqrt(n)) ^ inv(oftype(n, length(me))) - set!(mts, pe, sf .* me) - set!(mts, reverse(pe), sf .* mer) - end - return bp_cache + return bp_cache end diff --git a/src/contract.jl b/src/contract.jl index 34d03728..2f663a84 100644 --- a/src/contract.jl +++ b/src/contract.jl @@ -4,68 +4,68 @@ using LinearAlgebra: normalize! using NamedGraphs: NamedGraphs using NamedGraphs.OrdinalIndexing: th -function NDTensors.contract(tn::AbstractITensorNetwork; alg="exact", kwargs...) - return contract(Algorithm(alg), tn; kwargs...) +function NDTensors.contract(tn::AbstractITensorNetwork; alg = "exact", kwargs...) + return contract(Algorithm(alg), tn; kwargs...) end function NDTensors.contract( - alg::Algorithm"exact", - tn::AbstractITensorNetwork; - contraction_sequence_kwargs=(;), - sequence=contraction_sequence(tn; contraction_sequence_kwargs...), - kwargs..., -) - sequence_linear_index = deepmap(v -> NamedGraphs.vertex_positions(tn)[v], sequence) - ts = map(v -> tn[v], (1:nv(tn))th) - return contract(ts; sequence=sequence_linear_index, kwargs...) + alg::Algorithm"exact", + tn::AbstractITensorNetwork; + contraction_sequence_kwargs = (;), + sequence = contraction_sequence(tn; contraction_sequence_kwargs...), + kwargs..., + ) + sequence_linear_index = deepmap(v -> NamedGraphs.vertex_positions(tn)[v], sequence) + ts = map(v -> tn[v], (1:nv(tn))th) + return contract(ts; sequence = sequence_linear_index, kwargs...) end function NDTensors.contract( - alg::Union{Algorithm"density_matrix",Algorithm"ttn_svd"}, - tn::AbstractITensorNetwork; - output_structure::Function=path_graph_structure, - kwargs..., -) - return contract_approx(alg, tn, output_structure; kwargs...) + alg::Union{Algorithm"density_matrix", Algorithm"ttn_svd"}, + tn::AbstractITensorNetwork; + output_structure::Function = path_graph_structure, + kwargs..., + ) + return contract_approx(alg, tn, output_structure; kwargs...) end function ITensors.scalar(alg::Algorithm"exact", tn::AbstractITensorNetwork; kwargs...) - return contract(alg, tn; kwargs...)[] + return contract(alg, tn; kwargs...)[] end -function ITensors.scalar(tn::AbstractITensorNetwork; alg="exact", kwargs...) - return scalar(Algorithm(alg), tn; kwargs...) +function ITensors.scalar(tn::AbstractITensorNetwork; alg = "exact", kwargs...) + return scalar(Algorithm(alg), tn; kwargs...) end -function logscalar(tn::AbstractITensorNetwork; alg="exact", kwargs...) - return logscalar(Algorithm(alg), tn; kwargs...) +function logscalar(tn::AbstractITensorNetwork; alg = "exact", kwargs...) + return logscalar(Algorithm(alg), tn; kwargs...) end function logscalar(alg::Algorithm"exact", tn::AbstractITensorNetwork; kwargs...) - s = scalar(alg, tn; kwargs...) - s = real(s) < 0 ? complex(s) : s - return log(s) + s = scalar(alg, tn; kwargs...) + s = real(s) < 0 ? complex(s) : s + return log(s) end function logscalar( - alg::Algorithm, - tn::AbstractITensorNetwork; - (cache!)=nothing, - cache_construction_kwargs=default_cache_construction_kwargs(alg, tn), - update_cache=isnothing(cache!), - cache_update_kwargs=(;), -) - if isnothing(cache!) - cache! = Ref(cache(alg, tn; cache_construction_kwargs...)) - end + alg::Algorithm, + tn::AbstractITensorNetwork; + (cache!) = nothing, + cache_construction_kwargs = default_cache_construction_kwargs(alg, tn), + update_cache = isnothing(cache!), + cache_update_kwargs = (;), + ) + if isnothing(cache!) + cache! = Ref(cache(alg, tn; cache_construction_kwargs...)) + end - if update_cache - cache![] = update(cache![]; cache_update_kwargs...) - end + if update_cache + cache![] = update(cache![]; cache_update_kwargs...) + end - return logscalar(cache![]) + return logscalar(cache![]) end function ITensors.scalar(alg::Algorithm, tn::AbstractITensorNetwork; kwargs...) - return exp(logscalar(alg, tn; kwargs...)) + return exp(logscalar(alg, tn; kwargs...)) end diff --git a/src/contract_approx/binary_tree_partition.jl b/src/contract_approx/binary_tree_partition.jl index 9c269317..5fe715cc 100644 --- a/src/contract_approx/binary_tree_partition.jl +++ b/src/contract_approx/binary_tree_partition.jl @@ -2,57 +2,57 @@ using DataGraphs: DataGraph using ITensors: Index, ITensor, delta, replaceinds, sim using ITensors.NDTensors: Algorithm, @Algorithm_str using NamedGraphs.GraphsExtensions: - disjoint_union, - is_binary_arborescence, - is_leaf_vertex, - pre_order_dfs_vertices, - rename_vertices, - root_vertex, - subgraph + disjoint_union, + is_binary_arborescence, + is_leaf_vertex, + pre_order_dfs_vertices, + rename_vertices, + root_vertex, + subgraph function _binary_partition(tn::ITensorNetwork, source_inds::Vector{<:Index}) - external_inds = flatten_siteinds(tn) - # add delta tensor to each external ind - external_sim_ind = [sim(ind) for ind in external_inds] - tn = map_data(t -> replaceinds(t, external_inds => external_sim_ind), tn; edges=[]) - tn_wo_deltas = rename_vertices(v -> v[1], subgraph(v -> v[2] == 1, tn)) - deltas = collect(eachtensor(subgraph(v -> v[2] == 2, tn))) - scalars = rename_vertices(v -> v[1], subgraph(v -> v[2] == 3, tn)) - new_deltas = [ - delta(external_inds[i], external_sim_ind[i]) for i in 1:length(external_inds) - ] - # TODO: Combine in a more elegant way, so with `disjoint_union`. - deltas = [deltas..., new_deltas...] - tn = disjoint_union(tn_wo_deltas, ITensorNetwork(deltas), scalars) - p1, p2 = _mincut_partition_maxweightoutinds( - tn, source_inds, setdiff(external_inds, source_inds) - ) - source_tn = _contract_deltas(subgraph(tn, p1)) - remain_tn = _contract_deltas(subgraph(tn, p2)) - outinds_source = flatten_siteinds(source_tn) - outinds_remain = flatten_siteinds(remain_tn) - common_inds = intersect(outinds_source, outinds_remain) - @assert ( - length(external_inds) == - length(union(outinds_source, outinds_remain)) - length(common_inds) - ) - # We want the output two tns be connected to each other, so that the output - # of `binary_tree_partition` is a partition with a binary tree structure. - # Below we check if `source_tn` and `remain_tn` are connected, if not adding - # each tn a scalar tensor to force them to be connected. - if common_inds == [] - @info "_binary_partition outputs are not connected" - ind = Index(1, "unit_scalar_ind") - t1 = ITensor([1.0], ind) - t2 = ITensor([1.0], ind) - v1 = (nv(scalars) + 1, 3) - v2 = (nv(scalars) + 2, 3) - add_vertex!(source_tn, v1) - add_vertex!(remain_tn, v2) - source_tn[v1] = t1 - remain_tn[v2] = t2 - end - return source_tn, remain_tn + external_inds = flatten_siteinds(tn) + # add delta tensor to each external ind + external_sim_ind = [sim(ind) for ind in external_inds] + tn = map_data(t -> replaceinds(t, external_inds => external_sim_ind), tn; edges = []) + tn_wo_deltas = rename_vertices(v -> v[1], subgraph(v -> v[2] == 1, tn)) + deltas = collect(eachtensor(subgraph(v -> v[2] == 2, tn))) + scalars = rename_vertices(v -> v[1], subgraph(v -> v[2] == 3, tn)) + new_deltas = [ + delta(external_inds[i], external_sim_ind[i]) for i in 1:length(external_inds) + ] + # TODO: Combine in a more elegant way, so with `disjoint_union`. + deltas = [deltas..., new_deltas...] + tn = disjoint_union(tn_wo_deltas, ITensorNetwork(deltas), scalars) + p1, p2 = _mincut_partition_maxweightoutinds( + tn, source_inds, setdiff(external_inds, source_inds) + ) + source_tn = _contract_deltas(subgraph(tn, p1)) + remain_tn = _contract_deltas(subgraph(tn, p2)) + outinds_source = flatten_siteinds(source_tn) + outinds_remain = flatten_siteinds(remain_tn) + common_inds = intersect(outinds_source, outinds_remain) + @assert ( + length(external_inds) == + length(union(outinds_source, outinds_remain)) - length(common_inds) + ) + # We want the output two tns be connected to each other, so that the output + # of `binary_tree_partition` is a partition with a binary tree structure. + # Below we check if `source_tn` and `remain_tn` are connected, if not adding + # each tn a scalar tensor to force them to be connected. + if common_inds == [] + @info "_binary_partition outputs are not connected" + ind = Index(1, "unit_scalar_ind") + t1 = ITensor([1.0], ind) + t2 = ITensor([1.0], ind) + v1 = (nv(scalars) + 1, 3) + v2 = (nv(scalars) + 2, 3) + add_vertex!(source_tn, v1) + add_vertex!(remain_tn, v2) + source_tn[v1] = t1 + remain_tn[v2] = t2 + end + return source_tn, remain_tn end """ @@ -73,70 +73,70 @@ Note: name of vertices in the output partition are the same as the name of verti in `inds_btree`. """ function _partition( - ::Algorithm"mincut_recursive_bisection", tn::ITensorNetwork, inds_btree::DataGraph -) - @assert is_binary_arborescence(inds_btree) - output_tns = Vector{ITensorNetwork{vertextype(tn)}}() - output_deltas_vector = Vector{Vector{ITensor}}() - scalars_vector = Vector{Vector{ITensor}}() - # Mapping each vertex of the binary tree to a tn representing the partition - # of the subtree containing this vertex and its descendant vertices. - leaves = leaf_vertices(inds_btree) - root = root_vertex(inds_btree) - v_to_subtree_tn = Dict{eltype(inds_btree),ITensorNetwork{Tuple{vertextype(tn),Int}}}() - v_to_subtree_tn[root] = disjoint_union(tn, ITensorNetwork{vertextype(tn)}()) - for v in pre_order_dfs_vertices(inds_btree, root) - @assert haskey(v_to_subtree_tn, v) - input_tn = v_to_subtree_tn[v] - if !is_leaf_vertex(inds_btree, v) - c1, c2 = child_vertices(inds_btree, v) - descendant_c1 = pre_order_dfs_vertices(inds_btree, c1) - indices = [inds_btree[l] for l in intersect(descendant_c1, leaves)] - tn1, input_tn = _binary_partition(input_tn, indices) - v_to_subtree_tn[c1] = tn1 - descendant_c2 = pre_order_dfs_vertices(inds_btree, c2) - indices = [inds_btree[l] for l in intersect(descendant_c2, leaves)] - tn1, input_tn = _binary_partition(input_tn, indices) - v_to_subtree_tn[c2] = tn1 + ::Algorithm"mincut_recursive_bisection", tn::ITensorNetwork, inds_btree::DataGraph + ) + @assert is_binary_arborescence(inds_btree) + output_tns = Vector{ITensorNetwork{vertextype(tn)}}() + output_deltas_vector = Vector{Vector{ITensor}}() + scalars_vector = Vector{Vector{ITensor}}() + # Mapping each vertex of the binary tree to a tn representing the partition + # of the subtree containing this vertex and its descendant vertices. + leaves = leaf_vertices(inds_btree) + root = root_vertex(inds_btree) + v_to_subtree_tn = Dict{eltype(inds_btree), ITensorNetwork{Tuple{vertextype(tn), Int}}}() + v_to_subtree_tn[root] = disjoint_union(tn, ITensorNetwork{vertextype(tn)}()) + for v in pre_order_dfs_vertices(inds_btree, root) + @assert haskey(v_to_subtree_tn, v) + input_tn = v_to_subtree_tn[v] + if !is_leaf_vertex(inds_btree, v) + c1, c2 = child_vertices(inds_btree, v) + descendant_c1 = pre_order_dfs_vertices(inds_btree, c1) + indices = [inds_btree[l] for l in intersect(descendant_c1, leaves)] + tn1, input_tn = _binary_partition(input_tn, indices) + v_to_subtree_tn[c1] = tn1 + descendant_c2 = pre_order_dfs_vertices(inds_btree, c2) + indices = [inds_btree[l] for l in intersect(descendant_c2, leaves)] + tn1, input_tn = _binary_partition(input_tn, indices) + v_to_subtree_tn[c2] = tn1 + end + tn = rename_vertices(u -> u[1], subgraph(u -> u[2] == 1, input_tn)) + deltas = collect(eachtensor(subgraph(u -> u[2] == 2, input_tn))) + scalars = collect(eachtensor(subgraph(u -> u[2] == 3, input_tn))) + push!(output_tns, tn) + push!(output_deltas_vector, deltas) + push!(scalars_vector, scalars) + end + # In subgraph_vertices, each element is a vector of vertices to be + # grouped in one partition. + subgraph_vs = Vector{Vector{Tuple{vertextype(tn), Int}}}() + delta_num = 0 + scalar_num = 0 + for (tn, deltas, scalars) in zip(output_tns, output_deltas_vector, scalars_vector) + vs = [(v, 1) for v in vertices(tn)] + vs = vcat(vs, [(i + delta_num, 2) for i in 1:length(deltas)]) + vs = vcat(vs, [(i + scalar_num, 3) for i in 1:length(scalars)]) + push!(subgraph_vs, vs) + delta_num += length(deltas) + scalar_num += length(scalars) + end + out_tn = ITensorNetwork{vertextype(tn)}() + for tn in output_tns + for v in vertices(tn) + add_vertex!(out_tn, v) + out_tn[v] = tn[v] + end end - tn = rename_vertices(u -> u[1], subgraph(u -> u[2] == 1, input_tn)) - deltas = collect(eachtensor(subgraph(u -> u[2] == 2, input_tn))) - scalars = collect(eachtensor(subgraph(u -> u[2] == 3, input_tn))) - push!(output_tns, tn) - push!(output_deltas_vector, deltas) - push!(scalars_vector, scalars) - end - # In subgraph_vertices, each element is a vector of vertices to be - # grouped in one partition. - subgraph_vs = Vector{Vector{Tuple{vertextype(tn),Int}}}() - delta_num = 0 - scalar_num = 0 - for (tn, deltas, scalars) in zip(output_tns, output_deltas_vector, scalars_vector) - vs = [(v, 1) for v in vertices(tn)] - vs = vcat(vs, [(i + delta_num, 2) for i in 1:length(deltas)]) - vs = vcat(vs, [(i + scalar_num, 3) for i in 1:length(scalars)]) - push!(subgraph_vs, vs) - delta_num += length(deltas) - scalar_num += length(scalars) - end - out_tn = ITensorNetwork{vertextype(tn)}() - for tn in output_tns - for v in vertices(tn) - add_vertex!(out_tn, v) - out_tn[v] = tn[v] + tn_deltas = ITensorNetwork(vcat(output_deltas_vector...)) + tn_scalars = ITensorNetwork(vcat(scalars_vector...)) + par = _partition(disjoint_union(out_tn, tn_deltas, tn_scalars), subgraph_vs) + @assert is_tree(par) + name_map = Dict{Int, vertextype(tn)}() + for (i, v) in enumerate(pre_order_dfs_vertices(inds_btree, root)) + name_map[i] = v end - end - tn_deltas = ITensorNetwork(vcat(output_deltas_vector...)) - tn_scalars = ITensorNetwork(vcat(scalars_vector...)) - par = _partition(disjoint_union(out_tn, tn_deltas, tn_scalars), subgraph_vs) - @assert is_tree(par) - name_map = Dict{Int,vertextype(tn)}() - for (i, v) in enumerate(pre_order_dfs_vertices(inds_btree, root)) - name_map[i] = v - end - return rename_vertices(v -> name_map[v], par) + return rename_vertices(v -> name_map[v], par) end function _partition(tn::ITensorNetwork, inds_btree::DataGraph; alg) - return _partition(Algorithm(alg), tn, inds_btree) + return _partition(Algorithm(alg), tn, inds_btree) end diff --git a/src/contract_approx/contract_approx.jl b/src/contract_approx/contract_approx.jl index dc6a9b44..2423cae8 100644 --- a/src/contract_approx/contract_approx.jl +++ b/src/contract_approx/contract_approx.jl @@ -7,46 +7,46 @@ with the same binary tree structure. `root` is the root vertex of the pre-order depth-first-search traversal used to perform the truncations. """ function contract_approx( - ::Algorithm"density_matrix", - binary_tree_partition::DataGraph; - root, - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs, -) - @assert is_tree(binary_tree_partition) - @assert root in vertices(binary_tree_partition) - @assert is_binary_arborescence(dfs_tree(binary_tree_partition, root)) - # The `binary_tree_partition` may contain multiple delta tensors to make sure - # the partition has a binary tree structure. These delta tensors could hurt the - # performance when computing density matrices so we remove them first. - partition_wo_deltas = _contract_deltas_ignore_leaf_partitions( - binary_tree_partition; root=root - ) - return _approx_itensornetwork_density_matrix!( - partition_wo_deltas, - underlying_graph(binary_tree_partition); - root, - cutoff, - maxdim, - contraction_sequence_kwargs, - ) + ::Algorithm"density_matrix", + binary_tree_partition::DataGraph; + root, + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs, + ) + @assert is_tree(binary_tree_partition) + @assert root in vertices(binary_tree_partition) + @assert is_binary_arborescence(dfs_tree(binary_tree_partition, root)) + # The `binary_tree_partition` may contain multiple delta tensors to make sure + # the partition has a binary tree structure. These delta tensors could hurt the + # performance when computing density matrices so we remove them first. + partition_wo_deltas = _contract_deltas_ignore_leaf_partitions( + binary_tree_partition; root = root + ) + return _approx_itensornetwork_density_matrix!( + partition_wo_deltas, + underlying_graph(binary_tree_partition); + root, + cutoff, + maxdim, + contraction_sequence_kwargs, + ) end function contract_approx( - ::Algorithm"ttn_svd", - binary_tree_partition::DataGraph; - root, - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs, -) - @assert is_tree(binary_tree_partition) - @assert root in vertices(binary_tree_partition) - @assert is_binary_arborescence(dfs_tree(binary_tree_partition, root)) - return _approx_itensornetwork_ttn_svd!( - binary_tree_partition; root, cutoff, maxdim, contraction_sequence_kwargs - ) + ::Algorithm"ttn_svd", + binary_tree_partition::DataGraph; + root, + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs, + ) + @assert is_tree(binary_tree_partition) + @assert root in vertices(binary_tree_partition) + @assert is_binary_arborescence(dfs_tree(binary_tree_partition, root)) + return _approx_itensornetwork_ttn_svd!( + binary_tree_partition; root, cutoff, maxdim, contraction_sequence_kwargs + ) end """ @@ -55,22 +55,22 @@ with a binary tree structure. The binary tree structure is defined based on `inds_btree`, which is a directed binary tree DataGraph of indices. """ function contract_approx( - alg::Union{Algorithm"density_matrix",Algorithm"ttn_svd"}, - tn::ITensorNetwork, - inds_btree::DataGraph; - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs=(;), -) - par = _partition(tn, inds_btree; alg="mincut_recursive_bisection") - output_tn, log_root_norm = contract_approx( - alg, par; root=root_vertex(inds_btree), cutoff, maxdim, contraction_sequence_kwargs - ) - # Each leaf vertex in `output_tn` is adjacent to one output index. - # We remove these leaf vertices so that each non-root vertex in `output_tn` - # is an order 3 tensor. - _rem_leaf_vertices!(output_tn; root=root_vertex(inds_btree), contraction_sequence_kwargs) - return output_tn, log_root_norm + alg::Union{Algorithm"density_matrix", Algorithm"ttn_svd"}, + tn::ITensorNetwork, + inds_btree::DataGraph; + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs = (;), + ) + par = _partition(tn, inds_btree; alg = "mincut_recursive_bisection") + output_tn, log_root_norm = contract_approx( + alg, par; root = root_vertex(inds_btree), cutoff, maxdim, contraction_sequence_kwargs + ) + # Each leaf vertex in `output_tn` is adjacent to one output index. + # We remove these leaf vertices so that each non-root vertex in `output_tn` + # is an order 3 tensor. + _rem_leaf_vertices!(output_tn; root = root_vertex(inds_btree), contraction_sequence_kwargs) + return output_tn, log_root_norm end """ @@ -78,53 +78,53 @@ Approximate a given ITensorNetwork `tn` into an output ITensorNetwork with `outp `output_structure` outputs a directed binary tree DataGraph defining the desired graph structure. """ function contract_approx( - alg::Union{Algorithm"density_matrix",Algorithm"ttn_svd"}, - tn::ITensorNetwork, - output_structure::Function=path_graph_structure; - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs=(;), -) - inds_btree = output_structure(tn) - return contract_approx(alg, tn, inds_btree; cutoff, maxdim, contraction_sequence_kwargs) + alg::Union{Algorithm"density_matrix", Algorithm"ttn_svd"}, + tn::ITensorNetwork, + output_structure::Function = path_graph_structure; + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs = (;), + ) + inds_btree = output_structure(tn) + return contract_approx(alg, tn, inds_btree; cutoff, maxdim, contraction_sequence_kwargs) end # interface function contract_approx( - partitioned_tn::DataGraph; - alg::String, - root, - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs=(;), -) - return contract_approx( - Algorithm(alg), partitioned_tn; root, cutoff, maxdim, contraction_sequence_kwargs - ) + partitioned_tn::DataGraph; + alg::String, + root, + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs = (;), + ) + return contract_approx( + Algorithm(alg), partitioned_tn; root, cutoff, maxdim, contraction_sequence_kwargs + ) end function contract_approx( - tn::ITensorNetwork, - inds_btree::DataGraph; - alg::String, - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs=(;), -) - return contract_approx( - Algorithm(alg), tn, inds_btree; cutoff, maxdim, contraction_sequence_kwargs - ) + tn::ITensorNetwork, + inds_btree::DataGraph; + alg::String, + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs = (;), + ) + return contract_approx( + Algorithm(alg), tn, inds_btree; cutoff, maxdim, contraction_sequence_kwargs + ) end function contract_approx( - tn::ITensorNetwork, - output_structure::Function=path_graph_structure; - alg::String, - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs=(;), -) - return contract_approx( - Algorithm(alg), tn, output_structure; cutoff, maxdim, contraction_sequence_kwargs - ) + tn::ITensorNetwork, + output_structure::Function = path_graph_structure; + alg::String, + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs = (;), + ) + return contract_approx( + Algorithm(alg), tn, output_structure; cutoff, maxdim, contraction_sequence_kwargs + ) end diff --git a/src/contract_approx/contract_deltas.jl b/src/contract_approx/contract_deltas.jl index 6dfc61bf..45fc4a61 100644 --- a/src/contract_approx/contract_deltas.jl +++ b/src/contract_approx/contract_deltas.jl @@ -6,17 +6,17 @@ using .ITensorsExtensions: is_delta Rewrite of the function `DataStructures.root_union!(s::IntDisjointSet{T}, x::T, y::T) where {T<:Integer}`. """ -function _introot_union!(s::DataStructures.IntDisjointSets, x, y; left_root=true) - parents = s.parents - rks = s.ranks - @inbounds xrank = rks[x] - @inbounds yrank = rks[y] - if !left_root - x, y = y, x - end - @inbounds parents[y] = x - s.ngroups -= 1 - return x +function _introot_union!(s::DataStructures.IntDisjointSets, x, y; left_root = true) + parents = s.parents + rks = s.ranks + @inbounds xrank = rks[x] + @inbounds yrank = rks[y] + if !left_root + x, y = y, x + end + @inbounds parents[y] = x + s.ngroups -= 1 + return x end """ @@ -28,8 +28,8 @@ A specified root is useful in functions such as `_remove_deltas`, where when we indices into one disjointset, we want the index that is the outinds if the given tensor network to always be the root in the DisjointSets. """ -function _root_union!(s::DisjointSets, x, y; left_root=true) - return s.revmap[_introot_union!(s.internal, s.intmap[x], s.intmap[y]; left_root=true)] +function _root_union!(s::DisjointSets, x, y; left_root = true) + return s.revmap[_introot_union!(s.internal, s.intmap[x], s.intmap[y]; left_root = true)] end """ @@ -39,21 +39,21 @@ If a disjoint set contains indices in `rootinds`, then one of such indices in `r must be the root of this set. """ function _delta_inds_disjointsets(deltas::Vector{<:ITensor}, rootinds::Vector{<:Index}) - if deltas == [] - return DisjointSets() - end - inds_list = map(t -> collect(inds(t)), deltas) - deltainds = collect(Set(vcat(inds_list...))) - ds = DisjointSets(deltainds) - for t in deltas - i1, i2 = inds(t) - if find_root!(ds, i1) in rootinds - _root_union!(ds, find_root!(ds, i1), find_root!(ds, i2)) - else - _root_union!(ds, find_root!(ds, i2), find_root!(ds, i1)) + if deltas == [] + return DisjointSets() + end + inds_list = map(t -> collect(inds(t)), deltas) + deltainds = collect(Set(vcat(inds_list...))) + ds = DisjointSets(deltainds) + for t in deltas + i1, i2 = inds(t) + if find_root!(ds, i1) in rootinds + _root_union!(ds, find_root!(ds, i1), find_root!(ds, i2)) + else + _root_union!(ds, find_root!(ds, i2), find_root!(ds, i1)) + end end - end - return ds + return ds end """ @@ -86,35 +86,35 @@ Example: 4 │ ((dim=2|id=626|"6"), (dim=2|id=237|"5")) """ function _contract_deltas(tn::ITensorNetwork) - deltas = filter(is_delta, collect(eachtensor(tn))) - if isempty(deltas) - return tn - end - tn = copy(tn) - outinds = flatten_siteinds(tn) - ds = _delta_inds_disjointsets(deltas, outinds) - deltainds = [ds...] - sim_deltainds = [find_root!(ds, i) for i in deltainds] - # `rem_vertex!(tn, v)` changes `vertices(tn)` in place. - # We copy it here so that the enumeration won't be affected. - vs = copy(vertices(tn)) - for v in vs - if !is_delta(tn[v]) - tn[v] = replaceinds(tn[v], deltainds, sim_deltainds) - continue + deltas = filter(is_delta, collect(eachtensor(tn))) + if isempty(deltas) + return tn end - i1, i2 = inds(tn[v]) - root = find_root!(ds, i1) - @assert root == find_root!(ds, i2) - if i1 != root && i1 in outinds - tn[v] = delta(i1, root) - elseif i2 != root && i2 in outinds - tn[v] = delta(i2, root) - else - rem_vertex!(tn, v) + tn = copy(tn) + outinds = flatten_siteinds(tn) + ds = _delta_inds_disjointsets(deltas, outinds) + deltainds = [ds...] + sim_deltainds = [find_root!(ds, i) for i in deltainds] + # `rem_vertex!(tn, v)` changes `vertices(tn)` in place. + # We copy it here so that the enumeration won't be affected. + vs = copy(vertices(tn)) + for v in vs + if !is_delta(tn[v]) + tn[v] = replaceinds(tn[v], deltainds, sim_deltainds) + continue + end + i1, i2 = inds(tn[v]) + root = find_root!(ds, i1) + @assert root == find_root!(ds, i2) + if i1 != root && i1 in outinds + tn[v] = delta(i1, root) + elseif i2 != root && i2 in outinds + tn[v] = delta(i2, root) + else + rem_vertex!(tn, v) + end end - end - return tn + return tn end """ @@ -127,34 +127,34 @@ Note: only delta tensors of non-leaf vertices will be contracted. Note: this function assumes that all noncommoninds of the partition are in leaf partitions. """ function _contract_deltas_ignore_leaf_partitions( - partition::DataGraph; root=first(vertices(partition)) -) - partition = copy(partition) - leaves = leaf_vertices(dfs_tree(partition, root)) - nonleaves = setdiff(vertices(partition), leaves) - rootinds = _noncommoninds(subgraph(partition, nonleaves)) - # check rootinds are not noncommoninds of the partition - @assert isempty(intersect(rootinds, _noncommoninds(partition))) - nonleaves_tn = _contract_deltas(reduce(union, [partition[v] for v in nonleaves])) - nondelta_vs = filter(v -> !is_delta(nonleaves_tn[v]), vertices(nonleaves_tn)) - for v in nonleaves - partition[v] = subgraph(nonleaves_tn, intersect(nondelta_vs, vertices(partition[v]))) - end - # Note: we also need to change inds in the leaves since they can be connected by deltas - # in nonleaf vertices - delta_vs = setdiff(vertices(nonleaves_tn), nondelta_vs) - if isempty(delta_vs) - return partition - end - ds = _delta_inds_disjointsets( - Vector{ITensor}(subgraph(nonleaves_tn, delta_vs)), Vector{Index}() - ) - deltainds = Index[ds...] - sim_deltainds = Index[find_root!(ds, ind) for ind in deltainds] - for tn_v in leaves - partition[tn_v] = map_data(partition[tn_v]; edges=[]) do t - return replaceinds(t, deltainds, sim_deltainds) + partition::DataGraph; root = first(vertices(partition)) + ) + partition = copy(partition) + leaves = leaf_vertices(dfs_tree(partition, root)) + nonleaves = setdiff(vertices(partition), leaves) + rootinds = _noncommoninds(subgraph(partition, nonleaves)) + # check rootinds are not noncommoninds of the partition + @assert isempty(intersect(rootinds, _noncommoninds(partition))) + nonleaves_tn = _contract_deltas(reduce(union, [partition[v] for v in nonleaves])) + nondelta_vs = filter(v -> !is_delta(nonleaves_tn[v]), vertices(nonleaves_tn)) + for v in nonleaves + partition[v] = subgraph(nonleaves_tn, intersect(nondelta_vs, vertices(partition[v]))) + end + # Note: we also need to change inds in the leaves since they can be connected by deltas + # in nonleaf vertices + delta_vs = setdiff(vertices(nonleaves_tn), nondelta_vs) + if isempty(delta_vs) + return partition end - end - return partition + ds = _delta_inds_disjointsets( + Vector{ITensor}(subgraph(nonleaves_tn, delta_vs)), Vector{Index}() + ) + deltainds = Index[ds...] + sim_deltainds = Index[find_root!(ds, ind) for ind in deltainds] + for tn_v in leaves + partition[tn_v] = map_data(partition[tn_v]; edges = []) do t + return replaceinds(t, deltainds, sim_deltainds) + end + end + return partition end diff --git a/src/contract_approx/density_matrix.jl b/src/contract_approx/density_matrix.jl index 9e745a8b..456afa96 100644 --- a/src/contract_approx/density_matrix.jl +++ b/src/contract_approx/density_matrix.jl @@ -13,13 +13,13 @@ Density matrix example: Consider a tensor network below, 1 /\ - 9 2 +9 2 / /\ - 3 6 +3 6 /| /\ - 4 5 7 8 +4 5 7 8 / | | \ - The density matrix for the edge `NamedEdge(2, 3)` squares the subgraph with vertices 3, 4, 5 +The density matrix for the edge `NamedEdge(2, 3)` squares the subgraph with vertices 3, 4, 5 | 3 /| @@ -33,11 +33,11 @@ Density matrix example: with vertices 1, 2, 3, 4, 6, 7, 8, 9 1 /\ - / 2 +/ 2 / /\ - / 3 6 +/ 3 6 9 /| /\ - | 4 7 8 +| 4 7 8 | | | | | 4 7 8 | |/ | / @@ -52,11 +52,11 @@ Density matrix example: with vertices 1, 2, 3, 5, 6, 7, 8, 9 1 /\ - / 2 +/ 2 / /\ - / 3 6 +/ 3 6 9 /| /\ - | 5 7 8 +| 5 7 8 | | | | | 5 7 8 | |/ | / @@ -72,13 +72,13 @@ Partial density matrix example: Consider a tensor network below, 1 /\ - 9 2 +9 2 / /\ - 3 6 +3 6 /| /\ - 4 5 7 8 +4 5 7 8 / | | \ - The partial density matrix for the Edge set `Set([NamedEdge(2, 3), NamedEdge(5, 3)])` +The partial density matrix for the Edge set `Set([NamedEdge(2, 3), NamedEdge(5, 3)])` squares the subgraph with vertices 4, and contract with the tensor 3 | 3 @@ -88,11 +88,11 @@ Partial density matrix example: squares the subgraph with vertices 1, 2, 6, 7, 8, 9, and contract with the tensor 3 1 /\ - / 2 +/ 2 / /\ - / 3 6 +/ 3 6 9 /| /\ - | 7 8 +| 7 8 | | | | 7 8 | | / @@ -111,14 +111,14 @@ Partial density matrix example: 5 - 5 - """ struct _DensityMatrixAlgCaches - e_to_dm::Dict{NamedEdge,ITensor} - es_to_pdm::Dict{Set{NamedEdge},ITensor} + e_to_dm::Dict{NamedEdge, ITensor} + es_to_pdm::Dict{Set{NamedEdge}, ITensor} end function _DensityMatrixAlgCaches() - e_to_dm = Dict{NamedEdge,ITensor}() - es_to_pdm = Dict{Set{NamedEdge},ITensor}() - return _DensityMatrixAlgCaches(e_to_dm, es_to_pdm) + e_to_dm = Dict{NamedEdge, ITensor}() + es_to_pdm = Dict{Set{NamedEdge}, ITensor}() + return _DensityMatrixAlgCaches(e_to_dm, es_to_pdm) end """ @@ -130,39 +130,39 @@ The struct stores data used in the density matrix algorithm. caches: all the cached density matrices """ struct _DensityMartrixAlgGraph - partition::DataGraph - out_tree::NamedGraph - root::Any - innerinds_to_sim::Dict{<:Index,<:Index} - caches::_DensityMatrixAlgCaches + partition::DataGraph + out_tree::NamedGraph + root::Any + innerinds_to_sim::Dict{<:Index, <:Index} + caches::_DensityMatrixAlgCaches end function _DensityMartrixAlgGraph(partition::DataGraph, out_tree::NamedGraph, root::Any) - innerinds = _commoninds(partition) - sim_innerinds = [sim(ind) for ind in innerinds] - return _DensityMartrixAlgGraph( - partition, - out_tree, - root, - Dict(zip(innerinds, sim_innerinds)), - _DensityMatrixAlgCaches(), - ) + innerinds = _commoninds(partition) + sim_innerinds = [sim(ind) for ind in innerinds] + return _DensityMartrixAlgGraph( + partition, + out_tree, + root, + Dict(zip(innerinds, sim_innerinds)), + _DensityMatrixAlgCaches(), + ) end function _get_low_rank_projector(tensor, inds1, inds2; cutoff, maxdim) - @assert length(inds(tensor)) <= 4 - F = eigen(tensor, inds1, inds2; cutoff, maxdim, ishermitian=true) - return F.Vt + @assert length(inds(tensor)) <= 4 + F = eigen(tensor, inds1, inds2; cutoff, maxdim, ishermitian = true) + return F.Vt end """ Returns a dict that maps the partition's outinds that are adjacent to `partition[root]` to siminds """ function _densitymatrix_outinds_to_sim(partition, root) - outinds = _noncommoninds(partition) - outinds_root = intersect(outinds, flatten_siteinds(partition[root])) - outinds_root_to_sim = Dict(zip(outinds_root, [sim(ind) for ind in outinds_root])) - return outinds_root_to_sim + outinds = _noncommoninds(partition) + outinds_root = intersect(outinds, flatten_siteinds(partition[root])) + outinds_root_to_sim = Dict(zip(outinds_root, [sim(ind) for ind in outinds_root])) + return outinds_root_to_sim end """ @@ -171,15 +171,15 @@ corresponding value, and replace the inds that are in values of `inds_to_siminds to the corresponding key. """ function _sim(partial_dm_tensor::ITensor, inds_to_siminds) - siminds_to_inds = Dict(zip(values(inds_to_siminds), keys(inds_to_siminds))) - indices = keys(inds_to_siminds) - indices = intersect(indices, inds(partial_dm_tensor)) - simindices = setdiff(inds(partial_dm_tensor), indices) - reorder_inds = [indices..., simindices...] - reorder_siminds = vcat( - [inds_to_siminds[i] for i in indices], [siminds_to_inds[i] for i in simindices] - ) - return replaceinds(partial_dm_tensor, reorder_inds => reorder_siminds) + siminds_to_inds = Dict(zip(values(inds_to_siminds), keys(inds_to_siminds))) + indices = keys(inds_to_siminds) + indices = intersect(indices, inds(partial_dm_tensor)) + simindices = setdiff(inds(partial_dm_tensor), indices) + reorder_inds = [indices..., simindices...] + reorder_siminds = vcat( + [inds_to_siminds[i] for i in indices], [siminds_to_inds[i] for i in simindices] + ) + return replaceinds(partial_dm_tensor, reorder_inds => reorder_siminds) end """ @@ -191,42 +191,42 @@ Update `caches.e_to_dm[e]` and `caches.es_to_pdm[es]`. inds_to_sim: a dict mapping inds to sim inds """ function _update!( - caches::_DensityMatrixAlgCaches, - edge::NamedEdge, - children::Vector, - network::Vector{ITensor}, - inds_to_sim; - contraction_sequence_kwargs, -) - v = dst(edge) - if haskey(caches.e_to_dm, edge) - return nothing - end - child_to_dm = [c => caches.e_to_dm[NamedEdge(v, c)] for c in children] - pdms = [] - for (child_v, dm_tensor) in child_to_dm - es = [NamedEdge(src_v, v) for src_v in setdiff(children, child_v)] - es = Set(vcat(es, [edge])) - if !haskey(caches.es_to_pdm, es) - caches.es_to_pdm[es] = _optcontract([dm_tensor; network]; contraction_sequence_kwargs) + caches::_DensityMatrixAlgCaches, + edge::NamedEdge, + children::Vector, + network::Vector{ITensor}, + inds_to_sim; + contraction_sequence_kwargs, + ) + v = dst(edge) + if haskey(caches.e_to_dm, edge) + return nothing + end + child_to_dm = [c => caches.e_to_dm[NamedEdge(v, c)] for c in children] + pdms = [] + for (child_v, dm_tensor) in child_to_dm + es = [NamedEdge(src_v, v) for src_v in setdiff(children, child_v)] + es = Set(vcat(es, [edge])) + if !haskey(caches.es_to_pdm, es) + caches.es_to_pdm[es] = _optcontract([dm_tensor; network]; contraction_sequence_kwargs) + end + push!(pdms, caches.es_to_pdm[es]) end - push!(pdms, caches.es_to_pdm[es]) - end - if length(pdms) == 0 - sim_network = map(x -> replaceinds(x, inds_to_sim), network) - sim_network = map(dag, sim_network) - density_matrix = _optcontract([network; sim_network]; contraction_sequence_kwargs) - elseif length(pdms) == 1 - sim_network = map(x -> replaceinds(x, inds_to_sim), network) - sim_network = map(dag, sim_network) - density_matrix = _optcontract([pdms[1]; sim_network]; contraction_sequence_kwargs) - else - simtensor = _sim(pdms[2], inds_to_sim) - simtensor = dag(simtensor) - density_matrix = _optcontract([pdms[1], simtensor]; contraction_sequence_kwargs) - end - caches.e_to_dm[edge] = density_matrix - return nothing + if length(pdms) == 0 + sim_network = map(x -> replaceinds(x, inds_to_sim), network) + sim_network = map(dag, sim_network) + density_matrix = _optcontract([network; sim_network]; contraction_sequence_kwargs) + elseif length(pdms) == 1 + sim_network = map(x -> replaceinds(x, inds_to_sim), network) + sim_network = map(dag, sim_network) + density_matrix = _optcontract([pdms[1]; sim_network]; contraction_sequence_kwargs) + else + simtensor = _sim(pdms[2], inds_to_sim) + simtensor = dag(simtensor) + density_matrix = _optcontract([pdms[1], simtensor]; contraction_sequence_kwargs) + end + caches.e_to_dm[edge] = density_matrix + return nothing end """ @@ -237,88 +237,88 @@ Example: Consider an `alg_graph`` whose `out_tree` is shown below, 1 /\ - 9 2 +9 2 / /\ - 3 6 +3 6 /| /\ - 4 5 7 8 +4 5 7 8 / | | \ - when `root = 4`, the output `out_tree` will be +when `root = 4`, the output `out_tree` will be 1 /\ - 9 2 +9 2 / /\ - 3 6 +3 6 /| /\ - 5 7 8 +5 7 8 | | \ - and the returned tensor `U` will be the projector at vertex 4 in the output tn. +and the returned tensor `U` will be the projector at vertex 4 in the output tn. """ function _rem_vertex!( - alg_graph::_DensityMartrixAlgGraph, root; cutoff, maxdim, contraction_sequence_kwargs -) - caches = alg_graph.caches - outinds_root_to_sim = _densitymatrix_outinds_to_sim(alg_graph.partition, root) - inds_to_sim = merge(alg_graph.innerinds_to_sim, outinds_root_to_sim) - dm_dfs_tree = dfs_tree(alg_graph.out_tree, root) - @assert length(child_vertices(dm_dfs_tree, root)) == 1 - for v in post_order_dfs_vertices(dm_dfs_tree, root) - children = sort(child_vertices(dm_dfs_tree, v)) - @assert length(children) <= 2 - network = collect(eachtensor(alg_graph.partition[v])) - _update!( - caches, - NamedEdge(parent_vertex(dm_dfs_tree, v), v), - children, - network, - inds_to_sim; - contraction_sequence_kwargs, + alg_graph::_DensityMartrixAlgGraph, root; cutoff, maxdim, contraction_sequence_kwargs ) - end - U = _get_low_rank_projector( - caches.e_to_dm[NamedEdge(nothing, root)], - collect(keys(outinds_root_to_sim)), - collect(values(outinds_root_to_sim)); - cutoff, - maxdim, - ) - # update partition and out_tree - root_tensor = _optcontract( - [collect(eachtensor(alg_graph.partition[root])); dag(U)]; contraction_sequence_kwargs - ) - new_root = child_vertices(dm_dfs_tree, root)[1] - alg_graph.partition[new_root] = disjoint_union( - alg_graph.partition[new_root], ITensorNetwork([root_tensor]) - ) - rem_vertex!(alg_graph.partition, root) - rem_vertex!(alg_graph.out_tree, root) - # update es_to_pdm - truncate_dfs_tree = dfs_tree(alg_graph.out_tree, alg_graph.root) - for es in filter(es -> dst(first(es)) == root, keys(caches.es_to_pdm)) - delete!(caches.es_to_pdm, es) - end - for es in filter(es -> dst(first(es)) == new_root, keys(caches.es_to_pdm)) - parent_edge = NamedEdge(parent_vertex(truncate_dfs_tree, new_root), new_root) - edge_to_remove = NamedEdge(root, new_root) - if intersect(es, Set([parent_edge])) == Set() - new_es = setdiff(es, [edge_to_remove]) - if new_es == Set() - new_es = Set([NamedEdge(nothing, new_root)]) - end - @assert length(new_es) >= 1 - caches.es_to_pdm[new_es] = _optcontract( - [caches.es_to_pdm[es], root_tensor]; contraction_sequence_kwargs - ) + caches = alg_graph.caches + outinds_root_to_sim = _densitymatrix_outinds_to_sim(alg_graph.partition, root) + inds_to_sim = merge(alg_graph.innerinds_to_sim, outinds_root_to_sim) + dm_dfs_tree = dfs_tree(alg_graph.out_tree, root) + @assert length(child_vertices(dm_dfs_tree, root)) == 1 + for v in post_order_dfs_vertices(dm_dfs_tree, root) + children = sort(child_vertices(dm_dfs_tree, v)) + @assert length(children) <= 2 + network = collect(eachtensor(alg_graph.partition[v])) + _update!( + caches, + NamedEdge(parent_vertex(dm_dfs_tree, v), v), + children, + network, + inds_to_sim; + contraction_sequence_kwargs, + ) end - # Remove old caches since they won't be used anymore, - # and removing them saves later contraction costs. - delete!(caches.es_to_pdm, es) - end - # update e_to_dm - for edge in filter(e -> dst(e) in [root, new_root], keys(caches.e_to_dm)) - delete!(caches.e_to_dm, edge) - end - return U + U = _get_low_rank_projector( + caches.e_to_dm[NamedEdge(nothing, root)], + collect(keys(outinds_root_to_sim)), + collect(values(outinds_root_to_sim)); + cutoff, + maxdim, + ) + # update partition and out_tree + root_tensor = _optcontract( + [collect(eachtensor(alg_graph.partition[root])); dag(U)]; contraction_sequence_kwargs + ) + new_root = child_vertices(dm_dfs_tree, root)[1] + alg_graph.partition[new_root] = disjoint_union( + alg_graph.partition[new_root], ITensorNetwork([root_tensor]) + ) + rem_vertex!(alg_graph.partition, root) + rem_vertex!(alg_graph.out_tree, root) + # update es_to_pdm + truncate_dfs_tree = dfs_tree(alg_graph.out_tree, alg_graph.root) + for es in filter(es -> dst(first(es)) == root, keys(caches.es_to_pdm)) + delete!(caches.es_to_pdm, es) + end + for es in filter(es -> dst(first(es)) == new_root, keys(caches.es_to_pdm)) + parent_edge = NamedEdge(parent_vertex(truncate_dfs_tree, new_root), new_root) + edge_to_remove = NamedEdge(root, new_root) + if intersect(es, Set([parent_edge])) == Set() + new_es = setdiff(es, [edge_to_remove]) + if new_es == Set() + new_es = Set([NamedEdge(nothing, new_root)]) + end + @assert length(new_es) >= 1 + caches.es_to_pdm[new_es] = _optcontract( + [caches.es_to_pdm[es], root_tensor]; contraction_sequence_kwargs + ) + end + # Remove old caches since they won't be used anymore, + # and removing them saves later contraction costs. + delete!(caches.es_to_pdm, es) + end + # update e_to_dm + for edge in filter(e -> dst(e) in [root, new_root], keys(caches.e_to_dm)) + delete!(caches.e_to_dm, edge) + end + return U end """ @@ -326,35 +326,35 @@ Approximate a `partition` into an output ITensorNetwork with the binary tree structure defined by `out_tree`. """ function _approx_itensornetwork_density_matrix!( - input_partition::DataGraph, - out_tree::NamedGraph; - root=first(vertices(partition)), - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs, -) - # Change type of each partition[v] since they will be updated - # with potential data type chage. - partition = DataGraph(NamedGraph()) - for v in vertices(input_partition) - add_vertex!(partition, v) - partition[v] = ITensorNetwork{Any}(input_partition[v]) - end - @assert sort(vertices(partition)) == sort(vertices(out_tree)) - alg_graph = _DensityMartrixAlgGraph(partition, out_tree, root) - output_tn = ITensorNetwork() - for v in post_order_dfs_vertices(out_tree, root)[1:(end - 1)] - U = _rem_vertex!(alg_graph, v; cutoff, maxdim, contraction_sequence_kwargs) - add_vertex!(output_tn, v) - output_tn[v] = U - end - @assert length(vertices(partition)) == 1 - add_vertex!(output_tn, root) - root_tensor = _optcontract( - collect(eachtensor(partition[root])); contraction_sequence_kwargs - ) - root_norm = norm(root_tensor) - root_tensor /= root_norm - output_tn[root] = root_tensor - return output_tn, log(root_norm) + input_partition::DataGraph, + out_tree::NamedGraph; + root = first(vertices(partition)), + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs, + ) + # Change type of each partition[v] since they will be updated + # with potential data type chage. + partition = DataGraph(NamedGraph()) + for v in vertices(input_partition) + add_vertex!(partition, v) + partition[v] = ITensorNetwork{Any}(input_partition[v]) + end + @assert sort(vertices(partition)) == sort(vertices(out_tree)) + alg_graph = _DensityMartrixAlgGraph(partition, out_tree, root) + output_tn = ITensorNetwork() + for v in post_order_dfs_vertices(out_tree, root)[1:(end - 1)] + U = _rem_vertex!(alg_graph, v; cutoff, maxdim, contraction_sequence_kwargs) + add_vertex!(output_tn, v) + output_tn[v] = U + end + @assert length(vertices(partition)) == 1 + add_vertex!(output_tn, root) + root_tensor = _optcontract( + collect(eachtensor(partition[root])); contraction_sequence_kwargs + ) + root_norm = norm(root_tensor) + root_tensor /= root_norm + output_tn[root] = root_tensor + return output_tn, log(root_norm) end diff --git a/src/contract_approx/mincut.jl b/src/contract_approx/mincut.jl index 6c772894..1233afc9 100644 --- a/src/contract_approx/mincut.jl +++ b/src/contract_approx/mincut.jl @@ -5,13 +5,13 @@ using NamedGraphs: NamedDiGraph using NDTensors.AlgorithmSelection: Algorithm # a large number to prevent this edge being a cut -MAX_WEIGHT = 1e32 +MAX_WEIGHT = 1.0e32 """ Outputs a maximimally unbalanced directed binary tree DataGraph defining the desired graph structure """ function path_graph_structure(tn::ITensorNetwork) - return path_graph_structure(tn, flatten_siteinds(tn)) + return path_graph_structure(tn, flatten_siteinds(tn)) end """ @@ -19,14 +19,14 @@ Given a `tn` and `outinds` (a subset of noncommoninds of `tn`), outputs a maximi directed binary tree DataGraph of `outinds` defining the desired graph structure """ function path_graph_structure(tn::ITensorNetwork, outinds::Vector) - return _binary_tree_structure(tn, outinds; maximally_unbalanced=true) + return _binary_tree_structure(tn, outinds; maximally_unbalanced = true) end """ Outputs a directed binary tree DataGraph defining the desired graph structure """ function binary_tree_structure(tn::ITensorNetwork) - return binary_tree_structure(tn, flatten_siteinds(tn)) + return binary_tree_structure(tn, flatten_siteinds(tn)) end """ @@ -34,19 +34,19 @@ Given a `tn` and `outinds` (a subset of noncommoninds of `tn`), outputs a directed binary tree DataGraph of `outinds` defining the desired graph structure """ function binary_tree_structure(tn::ITensorNetwork, outinds::Vector) - return _binary_tree_structure(tn, outinds; maximally_unbalanced=false) + return _binary_tree_structure(tn, outinds; maximally_unbalanced = false) end function mincut(graph::AbstractGraph, source_vertex, target_vertex; backend, kwargs...) - # TODO: Replace with `Backend(backend)`. - return mincut(Algorithm(backend), graph, source_vertex, target_vertex; kwargs...) + # TODO: Replace with `Backend(backend)`. + return mincut(Algorithm(backend), graph, source_vertex, target_vertex; kwargs...) end # TODO: Replace with `backend::Backend`. function mincut( - backend::Algorithm, graph::AbstractGraph, source_vertex, target_vertex; kwargs... -) - return error("Backend `$backend` not implemented for `mincut`.") + backend::Algorithm, graph::AbstractGraph, source_vertex, target_vertex; kwargs... + ) + return error("Backend `$backend` not implemented for `mincut`.") end """ @@ -56,15 +56,15 @@ Mincut of two inds list is defined as the mincut of two newly added vertices, each one neighboring to one inds subset. """ function _mincut(tn::ITensorNetwork, source_inds::Vector, terminal_inds::Vector) - @assert length(source_inds) >= 1 - @assert length(terminal_inds) >= 1 - noncommon_inds = flatten_siteinds(tn) - @assert issubset(source_inds, noncommon_inds) - @assert issubset(terminal_inds, noncommon_inds) - tn = disjoint_union( - ITensorNetwork([ITensor(source_inds...), ITensor(terminal_inds...)]), tn - ) - return mincut(tn, (1, 1), (2, 1); backend="GraphsFlows", capacity_matrix=weights(tn)) + @assert length(source_inds) >= 1 + @assert length(terminal_inds) >= 1 + noncommon_inds = flatten_siteinds(tn) + @assert issubset(source_inds, noncommon_inds) + @assert issubset(terminal_inds, noncommon_inds) + tn = disjoint_union( + ITensorNetwork([ITensor(source_inds...), ITensor(terminal_inds...)]), tn + ) + return mincut(tn, (1, 1), (2, 1); backend = "GraphsFlows", capacity_matrix = weights(tn)) end """ @@ -72,40 +72,40 @@ Calculate the mincut_partitions between two subsets of the uncontracted inds (source_inds and terminal_inds) of the input tn. """ function _mincut_partitions(tn::ITensorNetwork, source_inds::Vector, terminal_inds::Vector) - p1, p2, cut = _mincut(tn, source_inds, terminal_inds) - p1 = [v[1] for v in p1 if v[2] == 2] - p2 = [v[1] for v in p2 if v[2] == 2] - return p1, p2 + p1, p2, cut = _mincut(tn, source_inds, terminal_inds) + p1 = [v[1] for v in p1 if v[2] == 2] + p2 = [v[1] for v in p2 if v[2] == 2] + return p1, p2 end function _mincut_partition_maxweightoutinds( - tn::ITensorNetwork, source_inds::Vector, terminal_inds::Vector -) - tn, out_to_maxweight_ind = _maxweightoutinds_tn(tn, [source_inds..., terminal_inds...]) - source_inds = [out_to_maxweight_ind[i] for i in source_inds] - terminal_inds = [out_to_maxweight_ind[i] for i in terminal_inds] - return _mincut_partitions(tn, source_inds, terminal_inds) + tn::ITensorNetwork, source_inds::Vector, terminal_inds::Vector + ) + tn, out_to_maxweight_ind = _maxweightoutinds_tn(tn, [source_inds..., terminal_inds...]) + source_inds = [out_to_maxweight_ind[i] for i in source_inds] + terminal_inds = [out_to_maxweight_ind[i] for i in terminal_inds] + return _mincut_partitions(tn, source_inds, terminal_inds) end """ Sum of shortest path distances among all outinds. """ function _distance(tn::ITensorNetwork, outinds::Vector) - @assert length(outinds) >= 1 - @assert issubset(outinds, flatten_siteinds(tn)) - if length(outinds) == 1 - return 0.0 - end - new_tensors = [ITensor(i) for i in outinds] - tn = disjoint_union(ITensorNetwork(new_tensors), tn) - distances = 0.0 - for i in 1:(length(new_tensors) - 1) - ds = dijkstra_shortest_paths(tn, [(i, 1)], weights(tn)) - for j in (i + 1):length(new_tensors) - distances += ds.dists[(j, 1)] + @assert length(outinds) >= 1 + @assert issubset(outinds, flatten_siteinds(tn)) + if length(outinds) == 1 + return 0.0 + end + new_tensors = [ITensor(i) for i in outinds] + tn = disjoint_union(ITensorNetwork(new_tensors), tn) + distances = 0.0 + for i in 1:(length(new_tensors) - 1) + ds = dijkstra_shortest_paths(tn, [(i, 1)], weights(tn)) + for j in (i + 1):length(new_tensors) + distances += ds.dists[(j, 1)] + end end - end - return distances + return distances end """ @@ -113,21 +113,21 @@ create a tn with empty ITensors whose outinds weights are MAX_WEIGHT The maxweight_tn is constructed so that only commoninds of the tn will be considered in mincut. """ -function _maxweightoutinds_tn(tn::ITensorNetwork, outinds::Union{Nothing,Vector}) - @assert issubset(outinds, flatten_siteinds(tn)) - out_to_maxweight_ind = Dict{Index,Index}() - for ind in outinds - out_to_maxweight_ind[ind] = Index(MAX_WEIGHT, ind.tags) - end - maxweight_tn = copy(tn) - for v in vertices(maxweight_tn) - t = maxweight_tn[v] - inds1 = [i for i in inds(t) if !(i in outinds)] - inds2 = [out_to_maxweight_ind[i] for i in inds(t) if i in outinds] - newt = ITensor(inds1..., inds2...) - maxweight_tn[v] = newt - end - return maxweight_tn, out_to_maxweight_ind +function _maxweightoutinds_tn(tn::ITensorNetwork, outinds::Union{Nothing, Vector}) + @assert issubset(outinds, flatten_siteinds(tn)) + out_to_maxweight_ind = Dict{Index, Index}() + for ind in outinds + out_to_maxweight_ind[ind] = Index(MAX_WEIGHT, ind.tags) + end + maxweight_tn = copy(tn) + for v in vertices(maxweight_tn) + t = maxweight_tn[v] + inds1 = [i for i in inds(t) if !(i in outinds)] + inds2 = [out_to_maxweight_ind[i] for i in inds(t) if i in outinds] + newt = ITensor(inds1..., inds2...) + maxweight_tn[v] = newt + end + return maxweight_tn, out_to_maxweight_ind end """ @@ -140,111 +140,111 @@ Example: # TODO """ function _binary_tree_structure( - tn::ITensorNetwork, outinds::Vector; maximally_unbalanced::Bool=false -) - inds_tree_vector = _binary_tree_partition_inds( - tn, outinds; maximally_unbalanced=maximally_unbalanced - ) - return _nested_vector_to_directed_tree(inds_tree_vector) + tn::ITensorNetwork, outinds::Vector; maximally_unbalanced::Bool = false + ) + inds_tree_vector = _binary_tree_partition_inds( + tn, outinds; maximally_unbalanced = maximally_unbalanced + ) + return _nested_vector_to_directed_tree(inds_tree_vector) end function _binary_tree_partition_inds( - tn::ITensorNetwork, outinds::Vector; maximally_unbalanced::Bool=false -) - if length(outinds) == 1 - return outinds - end - maxweight_tn, out_to_maxweight_ind = _maxweightoutinds_tn(tn, outinds) - tn_pair = tn => maxweight_tn - if maximally_unbalanced == false - return _binary_tree_partition_inds_mincut(tn_pair, out_to_maxweight_ind) - else - return line_to_tree( - _binary_tree_partition_inds_maximally_unbalanced(tn_pair, out_to_maxweight_ind) + tn::ITensorNetwork, outinds::Vector; maximally_unbalanced::Bool = false ) - end + if length(outinds) == 1 + return outinds + end + maxweight_tn, out_to_maxweight_ind = _maxweightoutinds_tn(tn, outinds) + tn_pair = tn => maxweight_tn + if maximally_unbalanced == false + return _binary_tree_partition_inds_mincut(tn_pair, out_to_maxweight_ind) + else + return line_to_tree( + _binary_tree_partition_inds_maximally_unbalanced(tn_pair, out_to_maxweight_ind) + ) + end end function _nested_vector_to_directed_tree(inds_tree_vector::Vector) - if length(inds_tree_vector) == 1 && inds_tree_vector[1] isa Index - inds_btree = DataGraph(NamedDiGraph([1]), Index) - inds_btree[1] = inds_tree_vector[1] - return inds_btree - end - treenode_to_v = Dict{Union{Vector,Index},Int}() - graph = DataGraph(NamedDiGraph(); edge_data_eltype=Index) - v = 1 - for treenode in PostOrderDFS(inds_tree_vector) - add_vertex!(graph, v) - treenode_to_v[treenode] = v - if treenode isa Index - graph[v] = treenode - else - @assert length(treenode) == 2 - add_edge!(graph, v, treenode_to_v[treenode[1]]) - add_edge!(graph, v, treenode_to_v[treenode[2]]) + if length(inds_tree_vector) == 1 && inds_tree_vector[1] isa Index + inds_btree = DataGraph(NamedDiGraph([1]), Index) + inds_btree[1] = inds_tree_vector[1] + return inds_btree + end + treenode_to_v = Dict{Union{Vector, Index}, Int}() + graph = DataGraph(NamedDiGraph(); edge_data_eltype = Index) + v = 1 + for treenode in PostOrderDFS(inds_tree_vector) + add_vertex!(graph, v) + treenode_to_v[treenode] = v + if treenode isa Index + graph[v] = treenode + else + @assert length(treenode) == 2 + add_edge!(graph, v, treenode_to_v[treenode[1]]) + add_edge!(graph, v, treenode_to_v[treenode[2]]) + end + v += 1 end - v += 1 - end - return graph + return graph end """ Given a tn and outinds, returns a vector of indices representing MPS inds ordering. """ -function _mps_partition_inds_order(tn::ITensorNetwork, outinds::Union{Nothing,Vector}) - if outinds == nothing - outinds = flatten_siteinds(tn) - end - if length(outinds) == 1 - return outinds - end - tn2, out_to_maxweight_ind = _maxweightoutinds_tn(tn, outinds) - return _binary_tree_partition_inds_maximally_unbalanced(tn => tn2, out_to_maxweight_ind) +function _mps_partition_inds_order(tn::ITensorNetwork, outinds::Union{Nothing, Vector}) + if outinds == nothing + outinds = flatten_siteinds(tn) + end + if length(outinds) == 1 + return outinds + end + tn2, out_to_maxweight_ind = _maxweightoutinds_tn(tn, outinds) + return _binary_tree_partition_inds_maximally_unbalanced(tn => tn2, out_to_maxweight_ind) end function _binary_tree_partition_inds_maximally_unbalanced( - tn_pair::Pair{<:ITensorNetwork,<:ITensorNetwork}, out_to_maxweight_ind::Dict{Index,Index} -) - outinds = collect(keys(out_to_maxweight_ind)) - @assert length(outinds) >= 1 - if length(outinds) <= 2 - return outinds - end - first_inds, _ = _mincut_inds( - tn_pair, out_to_maxweight_ind, collect(powerset(outinds, 1, 1)) - ) - first_ind = first_inds[1] - linear_order = [first_ind] - outinds = setdiff(outinds, linear_order) - while length(outinds) > 1 - sourceinds_list = [Vector{Index}([linear_order..., i]) for i in outinds] - target_inds, _ = _mincut_inds(tn_pair, out_to_maxweight_ind, sourceinds_list) - new_ind = setdiff(target_inds, linear_order)[1] - push!(linear_order, new_ind) - outinds = setdiff(outinds, [new_ind]) - end - push!(linear_order, outinds[1]) - return linear_order + tn_pair::Pair{<:ITensorNetwork, <:ITensorNetwork}, out_to_maxweight_ind::Dict{Index, Index} + ) + outinds = collect(keys(out_to_maxweight_ind)) + @assert length(outinds) >= 1 + if length(outinds) <= 2 + return outinds + end + first_inds, _ = _mincut_inds( + tn_pair, out_to_maxweight_ind, collect(powerset(outinds, 1, 1)) + ) + first_ind = first_inds[1] + linear_order = [first_ind] + outinds = setdiff(outinds, linear_order) + while length(outinds) > 1 + sourceinds_list = [Vector{Index}([linear_order..., i]) for i in outinds] + target_inds, _ = _mincut_inds(tn_pair, out_to_maxweight_ind, sourceinds_list) + new_ind = setdiff(target_inds, linear_order)[1] + push!(linear_order, new_ind) + outinds = setdiff(outinds, [new_ind]) + end + push!(linear_order, outinds[1]) + return linear_order end function _binary_tree_partition_inds_mincut( - tn_pair::Pair{<:ITensorNetwork,<:ITensorNetwork}, out_to_maxweight_ind::Dict{Index,Index} -) - outinds = collect(keys(out_to_maxweight_ind)) - @assert length(outinds) >= 1 - if length(outinds) <= 2 + tn_pair::Pair{<:ITensorNetwork, <:ITensorNetwork}, out_to_maxweight_ind::Dict{Index, Index} + ) + outinds = collect(keys(out_to_maxweight_ind)) + @assert length(outinds) >= 1 + if length(outinds) <= 2 + return outinds + end + while length(outinds) > 2 + tree_list = collect(powerset(outinds, 2, 2)) + sourceinds_list = [collect(Leaves(i)) for i in tree_list] + _, i = _mincut_inds(tn_pair, out_to_maxweight_ind, sourceinds_list) + tree = tree_list[i] + outinds = setdiff(outinds, tree) + outinds = vcat([tree], outinds) + end return outinds - end - while length(outinds) > 2 - tree_list = collect(powerset(outinds, 2, 2)) - sourceinds_list = [collect(Leaves(i)) for i in tree_list] - _, i = _mincut_inds(tn_pair, out_to_maxweight_ind, sourceinds_list) - tree = tree_list[i] - outinds = setdiff(outinds, tree) - outinds = vcat([tree], outinds) - end - return outinds end """ @@ -263,33 +263,33 @@ Note: the minimum under the condition that the first mincut is optimal, and the sourceinds have the lowest all-pair shortest path. """ function _mincut_inds( - tn_pair::Pair{<:ITensorNetwork,<:ITensorNetwork}, - out_to_maxweight_ind::Dict{<:Index,<:Index}, - sourceinds_list::Vector{<:Vector{<:Index}}, -) - function _mincut_value(tn, sinds, outinds) - tinds = setdiff(outinds, sinds) - _, _, cut = _mincut(tn, sinds, tinds) - return cut - end - function _get_weights(source_inds, outinds, maxweight_source_inds, maxweight_outinds) - mincut_val = _mincut_value(tn_pair.first, source_inds, outinds) - maxweight_mincut_val = _mincut_value( - tn_pair.second, maxweight_source_inds, maxweight_outinds + tn_pair::Pair{<:ITensorNetwork, <:ITensorNetwork}, + out_to_maxweight_ind::Dict{<:Index, <:Index}, + sourceinds_list::Vector{<:Vector{<:Index}}, ) - dist = _distance(tn_pair.first, source_inds) - return (mincut_val, maxweight_mincut_val, dist) - end + function _mincut_value(tn, sinds, outinds) + tinds = setdiff(outinds, sinds) + _, _, cut = _mincut(tn, sinds, tinds) + return cut + end + function _get_weights(source_inds, outinds, maxweight_source_inds, maxweight_outinds) + mincut_val = _mincut_value(tn_pair.first, source_inds, outinds) + maxweight_mincut_val = _mincut_value( + tn_pair.second, maxweight_source_inds, maxweight_outinds + ) + dist = _distance(tn_pair.first, source_inds) + return (mincut_val, maxweight_mincut_val, dist) + end - outinds = collect(keys(out_to_maxweight_ind)) - maxweight_outinds = collect(values(out_to_maxweight_ind)) - weights = [] - for source_inds in sourceinds_list - maxweight_source_inds = [out_to_maxweight_ind[i] for i in source_inds] - push!( - weights, _get_weights(source_inds, outinds, maxweight_source_inds, maxweight_outinds) - ) - end - i = argmin(weights) - return sourceinds_list[i], i + outinds = collect(keys(out_to_maxweight_ind)) + maxweight_outinds = collect(values(out_to_maxweight_ind)) + weights = [] + for source_inds in sourceinds_list + maxweight_source_inds = [out_to_maxweight_ind[i] for i in source_inds] + push!( + weights, _get_weights(source_inds, outinds, maxweight_source_inds, maxweight_outinds) + ) + end + i = argmin(weights) + return sourceinds_list[i], i end diff --git a/src/contract_approx/partition.jl b/src/contract_approx/partition.jl index 6a7ae702..d92ef6ca 100644 --- a/src/contract_approx/partition.jl +++ b/src/contract_approx/partition.jl @@ -6,61 +6,61 @@ using NamedGraphs: NamedGraph, subgraph using SplitApplyCombine: flatten function _partition(g::AbstractGraph, subgraph_vertices) - partitioned_graph = DataGraph( - NamedGraph(eachindex(subgraph_vertices)); - vertex_data_eltype=typeof(g), - edge_data_eltype=@NamedTuple{ - edges::Vector{edgetype(g)}, edge_data::Dictionary{edgetype(g),edge_data_eltype(g)} - } - ) - for v in vertices(partitioned_graph) - partitioned_graph[v] = subgraph(g, subgraph_vertices[v]) - end - for e in edges(g) - s1 = findfirst_on_vertices(subgraph -> src(e) ∈ vertices(subgraph), partitioned_graph) - s2 = findfirst_on_vertices(subgraph -> dst(e) ∈ vertices(subgraph), partitioned_graph) - if (!has_edge(partitioned_graph, s1, s2) && s1 ≠ s2) - add_edge!(partitioned_graph, s1, s2) - partitioned_graph[s1 => s2] = (; - edges=Vector{edgetype(g)}(), edge_data=Dictionary{edgetype(g),edge_data_eltype(g)}() - ) + partitioned_graph = DataGraph( + NamedGraph(eachindex(subgraph_vertices)); + vertex_data_eltype = typeof(g), + edge_data_eltype = @NamedTuple{ + edges::Vector{edgetype(g)}, edge_data::Dictionary{edgetype(g), edge_data_eltype(g)}, + } + ) + for v in vertices(partitioned_graph) + partitioned_graph[v] = subgraph(g, subgraph_vertices[v]) end - if has_edge(partitioned_graph, s1, s2) - push!(partitioned_graph[s1 => s2].edges, e) - if isassigned(g, e) - set!(partitioned_graph[s1 => s2].edge_data, e, g[e]) - end + for e in edges(g) + s1 = findfirst_on_vertices(subgraph -> src(e) ∈ vertices(subgraph), partitioned_graph) + s2 = findfirst_on_vertices(subgraph -> dst(e) ∈ vertices(subgraph), partitioned_graph) + if (!has_edge(partitioned_graph, s1, s2) && s1 ≠ s2) + add_edge!(partitioned_graph, s1, s2) + partitioned_graph[s1 => s2] = (; + edges = Vector{edgetype(g)}(), edge_data = Dictionary{edgetype(g), edge_data_eltype(g)}(), + ) + end + if has_edge(partitioned_graph, s1, s2) + push!(partitioned_graph[s1 => s2].edges, e) + if isassigned(g, e) + set!(partitioned_graph[s1 => s2].edge_data, e, g[e]) + end + end end - end - return partitioned_graph + return partitioned_graph end """ Find all vertices `v` such that `f(graph[v]) == true` """ function findall_on_vertices(f::Function, graph::AbstractDataGraph) - return findall(f, vertex_data(graph)) + return findall(f, vertex_data(graph)) end """ Find the vertex `v` such that `f(graph[v]) == true` """ function findfirst_on_vertices(f::Function, graph::AbstractDataGraph) - return findfirst(f, vertex_data(graph)) + return findfirst(f, vertex_data(graph)) end """ Find all edges `e` such that `f(graph[e]) == true` """ function findall_on_edges(f::Function, graph::AbstractDataGraph) - return findall(f, edge_data(graph)) + return findall(f, edge_data(graph)) end """ Find the edge `e` such that `f(graph[e]) == true` """ function findfirst_on_edges(f::Function, graph::AbstractDataGraph) - return findfirst(f, edge_data(graph)) + return findfirst(f, edge_data(graph)) end # function subgraphs(g::AbstractSimpleGraph, subgraph_vertices) @@ -93,12 +93,12 @@ end # end function _noncommoninds(partition::DataGraph) - tn = mapreduce(v -> collect(eachtensor(partition[v])), vcat, vertices(partition)) - return unique(flatten_siteinds(ITensorNetwork(tn))) + tn = mapreduce(v -> collect(eachtensor(partition[v])), vcat, vertices(partition)) + return unique(flatten_siteinds(ITensorNetwork(tn))) end # Util functions for partition function _commoninds(partition::DataGraph) - tn = mapreduce(v -> collect(eachtensor(partition[v])), vcat, vertices(partition)) - return unique(flatten_linkinds(ITensorNetwork(tn))) + tn = mapreduce(v -> collect(eachtensor(partition[v])), vcat, vertices(partition)) + return unique(flatten_linkinds(ITensorNetwork(tn))) end diff --git a/src/contract_approx/ttn_svd.jl b/src/contract_approx/ttn_svd.jl index 25e0a0e6..781116ef 100644 --- a/src/contract_approx/ttn_svd.jl +++ b/src/contract_approx/ttn_svd.jl @@ -10,22 +10,22 @@ first transforming the partition into a ttn, then truncating the ttn using a sequence of SVDs. """ function _approx_itensornetwork_ttn_svd!( - input_partition::DataGraph; - root=first(vertices(partition)), - cutoff=1e-15, - maxdim=10000, - contraction_sequence_kwargs, -) - tn = ITensorNetwork{vertextype(input_partition)}() - for v in vertices(input_partition) - add_vertex!(tn, v) - tn[v] = _optcontract( - collect(eachtensor(input_partition[v])); contraction_sequence_kwargs + input_partition::DataGraph; + root = first(vertices(partition)), + cutoff = 1.0e-15, + maxdim = 10000, + contraction_sequence_kwargs, ) - end - truncate_ttn = truncate(ttn(tn); cutoff, maxdim, root_vertex=root) - out_tn = ITensorNetwork(truncate_ttn) - root_norm = norm(out_tn[root]) - out_tn[root] /= root_norm - return out_tn, log(root_norm) + tn = ITensorNetwork{vertextype(input_partition)}() + for v in vertices(input_partition) + add_vertex!(tn, v) + tn[v] = _optcontract( + collect(eachtensor(input_partition[v])); contraction_sequence_kwargs + ) + end + truncate_ttn = truncate(ttn(tn); cutoff, maxdim, root_vertex = root) + out_tn = ITensorNetwork(truncate_ttn) + root_norm = norm(out_tn[root]) + out_tn[root] /= root_norm + return out_tn, log(root_norm) end diff --git a/src/contract_approx/utils.jl b/src/contract_approx/utils.jl index ecebb85d..d41380c3 100644 --- a/src/contract_approx/utils.jl +++ b/src/contract_approx/utils.jl @@ -9,26 +9,27 @@ In particular, the tensor of each leaf vertex is contracted with the tensor of i to keep the tensor unchanged. """ function _rem_leaf_vertices!( - tn::ITensorNetwork; root=first(vertices(tn)), contraction_sequence_kwargs -) - dfs_t = dfs_tree(tn, root) - leaves = leaf_vertices(dfs_t) - parents = [parent_vertex(dfs_t, leaf) for leaf in leaves] - for (l, p) in zip(leaves, parents) - tn[p] = _optcontract([tn[p], tn[l]]; contraction_sequence_kwargs) - rem_vertex!(tn, l) - end + tn::ITensorNetwork; root = first(vertices(tn)), contraction_sequence_kwargs + ) + dfs_t = dfs_tree(tn, root) + leaves = leaf_vertices(dfs_t) + parents = [parent_vertex(dfs_t, leaf) for leaf in leaves] + for (l, p) in zip(leaves, parents) + tn[p] = _optcontract([tn[p], tn[l]]; contraction_sequence_kwargs) + rem_vertex!(tn, l) + end + return end """ Contract of a vector of tensors, `network`, with a contraction sequence generated via sa_bipartite """ -function _optcontract(network::Vector; contraction_sequence_kwargs=(;)) - if length(network) == 0 - return ITensor(1) - end - @assert network isa Vector{ITensor} - sequence = contraction_sequence(network; contraction_sequence_kwargs...) - output = contract(network; sequence) - return output +function _optcontract(network::Vector; contraction_sequence_kwargs = (;)) + if length(network) == 0 + return ITensor(1) + end + @assert network isa Vector{ITensor} + sequence = contraction_sequence(network; contraction_sequence_kwargs...) + output = contract(network; sequence) + return output end diff --git a/src/contraction_sequences.jl b/src/contraction_sequences.jl index db67406c..d60d0caf 100644 --- a/src/contraction_sequences.jl +++ b/src/contraction_sequences.jl @@ -4,29 +4,29 @@ using ITensors.NDTensors: Algorithm, @Algorithm_str using NamedGraphs.Keys: Key using NamedGraphs.OrdinalIndexing: th -const ITensorList = Union{Vector{ITensor},Tuple{Vararg{ITensor}}} +const ITensorList = Union{Vector{ITensor}, Tuple{Vararg{ITensor}}} -function contraction_sequence(tn::ITensorList; alg="optimal", kwargs...) - return contraction_sequence(Algorithm(alg), tn; kwargs...) +function contraction_sequence(tn::ITensorList; alg = "optimal", kwargs...) + return contraction_sequence(Algorithm(alg), tn; kwargs...) end function contraction_sequence(alg::Algorithm, tn::ITensorList) - return throw( - ArgumentError( - "Algorithm $alg isn't defined for contraction sequence finding. Try loading a backend package like + return throw( + ArgumentError( + "Algorithm $alg isn't defined for contraction sequence finding. Try loading a backend package like TensorOperations.jl or OMEinsumContractionOrders.jl.", - ), - ) + ), + ) end -function deepmap(f, tree; filter=(x -> x isa AbstractArray)) - return filter(tree) ? map(t -> deepmap(f, t; filter=filter), tree) : f(tree) +function deepmap(f, tree; filter = (x -> x isa AbstractArray)) + return filter(tree) ? map(t -> deepmap(f, t; filter = filter), tree) : f(tree) end function contraction_sequence(tn::AbstractITensorNetwork; kwargs...) - # TODO: Use `token_vertex` and/or `token_vertices` here. - ts = map(v -> tn[v], (1:nv(tn))th) - seq_linear_index = contraction_sequence(ts; kwargs...) - # TODO: Use `Functors.fmap` or `StructWalk`? - return deepmap(n -> Key(vertices(tn)[n * th]), seq_linear_index) + # TODO: Use `token_vertex` and/or `token_vertices` here. + ts = map(v -> tn[v], (1:nv(tn))th) + seq_linear_index = contraction_sequence(ts; kwargs...) + # TODO: Use `Functors.fmap` or `StructWalk`? + return deepmap(n -> Key(vertices(tn)[n * th]), seq_linear_index) end diff --git a/src/edge_sequences.jl b/src/edge_sequences.jl index b8e55392..089a5218 100644 --- a/src/edge_sequences.jl +++ b/src/edge_sequences.jl @@ -7,40 +7,40 @@ using SimpleTraits: SimpleTraits, Not, @traitfn default_edge_sequence_alg() = "forest_cover" function default_edge_sequence(pg::PartitionedGraph) - return PartitionEdge.(edge_sequence(partitions_graph(pg))) + return PartitionEdge.(edge_sequence(partitions_graph(pg))) end @traitfn function edge_sequence( - g::::(!IsDirected); alg=default_edge_sequence_alg(), kwargs... -) - return edge_sequence(Algorithm(alg), g; kwargs...) + g::::(!IsDirected); alg = default_edge_sequence_alg(), kwargs... + ) + return edge_sequence(Algorithm(alg), g; kwargs...) end -@traitfn function edge_sequence(g::::IsDirected; alg=default_edge_sequence_alg(), kwargs...) - return edge_sequence(Algorithm(alg), undirected_graph(underlying_graph(g)); kwargs...) +@traitfn function edge_sequence(g::::IsDirected; alg = default_edge_sequence_alg(), kwargs...) + return edge_sequence(Algorithm(alg), undirected_graph(underlying_graph(g)); kwargs...) end @traitfn function edge_sequence(alg::Algorithm, g::::IsDirected; kwargs...) - return edge_sequence(alg, undirected_graph(underlying_graph(g)); kwargs...) + return edge_sequence(alg, undirected_graph(underlying_graph(g)); kwargs...) end @traitfn function edge_sequence( - ::Algorithm"forest_cover", - g::::(!IsDirected); - root_vertex=GraphsExtensions.default_root_vertex, -) - forests = forest_cover(g) - edges = edgetype(g)[] - for forest in forests - trees = [forest[vs] for vs in connected_components(forest)] - for tree in trees - tree_edges = post_order_dfs_edges(tree, root_vertex(tree)) - push!(edges, vcat(tree_edges, reverse(reverse.(tree_edges)))...) + ::Algorithm"forest_cover", + g::::(!IsDirected); + root_vertex = GraphsExtensions.default_root_vertex, + ) + forests = forest_cover(g) + edges = edgetype(g)[] + for forest in forests + trees = [forest[vs] for vs in connected_components(forest)] + for tree in trees + tree_edges = post_order_dfs_edges(tree, root_vertex(tree)) + push!(edges, vcat(tree_edges, reverse(reverse.(tree_edges)))...) + end end - end - return edges + return edges end @traitfn function edge_sequence(::Algorithm"parallel", g::::(!IsDirected)) - return [[e] for e in vcat(edges(g), reverse.(edges(g)))] + return [[e] for e in vcat(edges(g), reverse.(edges(g)))] end diff --git a/src/environment.jl b/src/environment.jl index b5f8b178..e6eb623a 100644 --- a/src/environment.jl +++ b/src/environment.jl @@ -4,46 +4,46 @@ using NamedGraphs.PartitionedGraphs: PartitionedGraph default_environment_algorithm() = "bp" function environment( - tn::AbstractITensorNetwork, - vertices::Vector; - alg=default_environment_algorithm(), - kwargs..., -) - return environment(Algorithm(alg), tn, vertices; kwargs...) + tn::AbstractITensorNetwork, + vertices::Vector; + alg = default_environment_algorithm(), + kwargs..., + ) + return environment(Algorithm(alg), tn, vertices; kwargs...) end function environment( - ::Algorithm"exact", tn::AbstractITensorNetwork, verts::Vector; kwargs... -) - return [contract(subgraph(tn, setdiff(vertices(tn), verts)); kwargs...)] + ::Algorithm"exact", tn::AbstractITensorNetwork, verts::Vector; kwargs... + ) + return [contract(subgraph(tn, setdiff(vertices(tn), verts)); kwargs...)] end function environment( - alg::Algorithm, - ptn::PartitionedGraph, - vertices::Vector; - (cache!)=nothing, - update_cache=isnothing(cache!), - cache_construction_kwargs=default_cache_construction_kwargs(alg, ptn), - cache_update_kwargs=(;), -) - if isnothing(cache!) - cache! = Ref(cache(alg, ptn; cache_construction_kwargs...)) - end + alg::Algorithm, + ptn::PartitionedGraph, + vertices::Vector; + (cache!) = nothing, + update_cache = isnothing(cache!), + cache_construction_kwargs = default_cache_construction_kwargs(alg, ptn), + cache_update_kwargs = (;), + ) + if isnothing(cache!) + cache! = Ref(cache(alg, ptn; cache_construction_kwargs...)) + end - if update_cache - cache![] = update(cache![]; cache_update_kwargs...) - end + if update_cache + cache![] = update(cache![]; cache_update_kwargs...) + end - return environment(cache![], vertices) + return environment(cache![], vertices) end function environment( - alg::Algorithm, - tn::AbstractITensorNetwork, - vertices::Vector; - partitioned_vertices=default_partitioned_vertices(tn), - kwargs..., -) - return environment(alg, PartitionedGraph(tn, partitioned_vertices), vertices; kwargs...) + alg::Algorithm, + tn::AbstractITensorNetwork, + vertices::Vector; + partitioned_vertices = default_partitioned_vertices(tn), + kwargs..., + ) + return environment(alg, PartitionedGraph(tn, partitioned_vertices), vertices; kwargs...) end diff --git a/src/expect.jl b/src/expect.jl index 142ea61a..0d259846 100644 --- a/src/expect.jl +++ b/src/expect.jl @@ -4,58 +4,58 @@ using ITensors: Op, op, contract, which_op default_expect_alg() = "bp" function expect(ψIψ::AbstractFormNetwork, op::Op; kwargs...) - v = only(op.sites) - ψIψ_v = ψIψ[operator_vertex(ψIψ, v)] - s = commonind(ψIψ[ket_vertex(ψIψ, v)], ψIψ_v) - operator = ITensors.op(op.which_op, s) - ∂ψIψ_∂v = environment(ψIψ, operator_vertices(ψIψ, [v]); kwargs...) - numerator_ts = vcat(∂ψIψ_∂v, operator) - denominator_ts = vcat(∂ψIψ_∂v, ψIψ_v) - numerator_seq = contraction_sequence(numerator_ts; alg="optimal") - denominator_seq = contraction_sequence(denominator_ts; alg="optimal") - numerator = contract(numerator_ts; sequence=numerator_seq)[] - denominator = contract(denominator_ts; sequence=denominator_seq)[] - - return numerator / denominator + v = only(op.sites) + ψIψ_v = ψIψ[operator_vertex(ψIψ, v)] + s = commonind(ψIψ[ket_vertex(ψIψ, v)], ψIψ_v) + operator = ITensors.op(op.which_op, s) + ∂ψIψ_∂v = environment(ψIψ, operator_vertices(ψIψ, [v]); kwargs...) + numerator_ts = vcat(∂ψIψ_∂v, operator) + denominator_ts = vcat(∂ψIψ_∂v, ψIψ_v) + numerator_seq = contraction_sequence(numerator_ts; alg = "optimal") + denominator_seq = contraction_sequence(denominator_ts; alg = "optimal") + numerator = contract(numerator_ts; sequence = numerator_seq)[] + denominator = contract(denominator_ts; sequence = denominator_seq)[] + + return numerator / denominator end function expect( - alg::Algorithm, - ψ::AbstractITensorNetwork, - ops; - (cache!)=nothing, - update_cache=isnothing(cache!), - cache_update_kwargs=(;), - cache_construction_kwargs=(;), - kwargs..., -) - ψIψ = QuadraticFormNetwork(ψ) - if isnothing(cache!) - cache! = Ref(cache(alg, ψIψ; cache_construction_kwargs...)) - end - - if update_cache - cache![] = update(cache![]; cache_update_kwargs...) - end - - return map(op -> expect(ψIψ, op; alg, cache!, update_cache=false, kwargs...), ops) + alg::Algorithm, + ψ::AbstractITensorNetwork, + ops; + (cache!) = nothing, + update_cache = isnothing(cache!), + cache_update_kwargs = (;), + cache_construction_kwargs = (;), + kwargs..., + ) + ψIψ = QuadraticFormNetwork(ψ) + if isnothing(cache!) + cache! = Ref(cache(alg, ψIψ; cache_construction_kwargs...)) + end + + if update_cache + cache![] = update(cache![]; cache_update_kwargs...) + end + + return map(op -> expect(ψIψ, op; alg, cache!, update_cache = false, kwargs...), ops) end function expect(alg::Algorithm"exact", ψ::AbstractITensorNetwork, ops; kwargs...) - ψIψ = QuadraticFormNetwork(ψ) - return map(op -> expect(ψIψ, op; alg, kwargs...), ops) + ψIψ = QuadraticFormNetwork(ψ) + return map(op -> expect(ψIψ, op; alg, kwargs...), ops) end -function expect(ψ::AbstractITensorNetwork, op::Op; alg=default_expect_alg(), kwargs...) - return expect(Algorithm(alg), ψ, [op]; kwargs...) +function expect(ψ::AbstractITensorNetwork, op::Op; alg = default_expect_alg(), kwargs...) + return expect(Algorithm(alg), ψ, [op]; kwargs...) end function expect( - ψ::AbstractITensorNetwork, op::String, vertices; alg=default_expect_alg(), kwargs... -) - return expect(Algorithm(alg), ψ, [Op(op, vertex) for vertex in vertices]; kwargs...) + ψ::AbstractITensorNetwork, op::String, vertices; alg = default_expect_alg(), kwargs... + ) + return expect(Algorithm(alg), ψ, [Op(op, vertex) for vertex in vertices]; kwargs...) end -function expect(ψ::AbstractITensorNetwork, op::String; alg=default_expect_alg(), kwargs...) - return expect(ψ, op, vertices(ψ); alg, kwargs...) +function expect(ψ::AbstractITensorNetwork, op::String; alg = default_expect_alg(), kwargs...) + return expect(ψ, op, vertices(ψ); alg, kwargs...) end diff --git a/src/formnetworks/abstractformnetwork.jl b/src/formnetworks/abstractformnetwork.jl index c163fcbf..07ef6939 100644 --- a/src/formnetworks/abstractformnetwork.jl +++ b/src/formnetworks/abstractformnetwork.jl @@ -17,66 +17,66 @@ bra_vertex_suffix(f::AbstractFormNetwork) = not_implemented() ket_vertex_suffix(f::AbstractFormNetwork) = not_implemented() function SimilarType.similar_type(f::AbstractFormNetwork) - return typeof(tensornetwork(f)) + return typeof(tensornetwork(f)) end # TODO: Use `NamedGraphs.GraphsExtensions.parent_graph_type`. function data_graph_type(f::AbstractFormNetwork) - return data_graph_type(tensornetwork(f)) + return data_graph_type(tensornetwork(f)) end # TODO: Use `NamedGraphs.GraphsExtensions.parent_graph`. data_graph(f::AbstractFormNetwork) = data_graph(tensornetwork(f)) function operator_vertices(f::AbstractFormNetwork) - return filter(v -> last(v) == operator_vertex_suffix(f), vertices(f)) + return filter(v -> last(v) == operator_vertex_suffix(f), vertices(f)) end function bra_vertices(f::AbstractFormNetwork) - return filter(v -> last(v) == bra_vertex_suffix(f), vertices(f)) + return filter(v -> last(v) == bra_vertex_suffix(f), vertices(f)) end function ket_vertices(f::AbstractFormNetwork) - return filter(v -> last(v) == ket_vertex_suffix(f), vertices(f)) + return filter(v -> last(v) == ket_vertex_suffix(f), vertices(f)) end function operator_vertices(f::AbstractFormNetwork, original_state_vertices::Vector) - return [operator_vertex_map(f)(osv) for osv in original_state_vertices] + return [operator_vertex_map(f)(osv) for osv in original_state_vertices] end function bra_vertices(f::AbstractFormNetwork, original_state_vertices::Vector) - return [bra_vertex_map(f)(osv) for osv in original_state_vertices] + return [bra_vertex_map(f)(osv) for osv in original_state_vertices] end function ket_vertices(f::AbstractFormNetwork, original_state_vertices::Vector) - return [ket_vertex_map(f)(osv) for osv in original_state_vertices] + return [ket_vertex_map(f)(osv) for osv in original_state_vertices] end function state_vertices(f::AbstractFormNetwork) - return vcat(bra_vertices(f), ket_vertices(f)) + return vcat(bra_vertices(f), ket_vertices(f)) end function state_vertices(f::AbstractFormNetwork, original_state_vertices::Vector) - return vcat( - bra_vertices(f, original_state_vertices), ket_vertices(f, original_state_vertices) - ) + return vcat( + bra_vertices(f, original_state_vertices), ket_vertices(f, original_state_vertices) + ) end function Graphs.induced_subgraph(f::AbstractFormNetwork, vertices::Vector) - return induced_subgraph(tensornetwork(f), vertices) + return induced_subgraph(tensornetwork(f), vertices) end function bra_network(f::AbstractFormNetwork) - return rename_vertices(inv_vertex_map(f), first(induced_subgraph(f, bra_vertices(f)))) + return rename_vertices(inv_vertex_map(f), first(induced_subgraph(f, bra_vertices(f)))) end function ket_network(f::AbstractFormNetwork) - return rename_vertices(inv_vertex_map(f), first(induced_subgraph(f, ket_vertices(f)))) + return rename_vertices(inv_vertex_map(f), first(induced_subgraph(f, ket_vertices(f)))) end function operator_network(f::AbstractFormNetwork) - return rename_vertices( - inv_vertex_map(f), first(induced_subgraph(f, operator_vertices(f))) - ) + return rename_vertices( + inv_vertex_map(f), first(induced_subgraph(f, operator_vertices(f))) + ) end operator_vertex_map(f::AbstractFormNetwork) = v -> (v, operator_vertex_suffix(f)) @@ -89,5 +89,5 @@ ket_vertex(f::AbstractFormNetwork, v) = ket_vertex_map(f)(v) original_state_vertex(f::AbstractFormNetwork, v) = inv_vertex_map(f)(v) function default_partitioned_vertices(f::AbstractFormNetwork) - return group(v -> original_state_vertex(f, v), vertices(f)) + return group(v -> original_state_vertex(f, v), vertices(f)) end diff --git a/src/formnetworks/bilinearformnetwork.jl b/src/formnetworks/bilinearformnetwork.jl index d34bd58d..16c303b5 100644 --- a/src/formnetworks/bilinearformnetwork.jl +++ b/src/formnetworks/bilinearformnetwork.jl @@ -6,37 +6,37 @@ default_dual_site_index_map = prime default_dual_link_index_map = sim struct BilinearFormNetwork{ - V, - TensorNetwork<:AbstractITensorNetwork{V}, - OperatorVertexSuffix, - BraVertexSuffix, - KetVertexSuffix, -} <: AbstractFormNetwork{V} - tensornetwork::TensorNetwork - operator_vertex_suffix::OperatorVertexSuffix - bra_vertex_suffix::BraVertexSuffix - ket_vertex_suffix::KetVertexSuffix + V, + TensorNetwork <: AbstractITensorNetwork{V}, + OperatorVertexSuffix, + BraVertexSuffix, + KetVertexSuffix, + } <: AbstractFormNetwork{V} + tensornetwork::TensorNetwork + operator_vertex_suffix::OperatorVertexSuffix + bra_vertex_suffix::BraVertexSuffix + ket_vertex_suffix::KetVertexSuffix end function BilinearFormNetwork( - operator::AbstractITensorNetwork, - bra::AbstractITensorNetwork, - ket::AbstractITensorNetwork; - operator_vertex_suffix=default_operator_vertex_suffix(), - bra_vertex_suffix=default_bra_vertex_suffix(), - ket_vertex_suffix=default_ket_vertex_suffix(), - dual_site_index_map=default_dual_site_index_map, - dual_link_index_map=default_dual_link_index_map, -) - bra_mapped = dual_link_index_map(dual_site_index_map(bra; links=[]); sites=[]) - tn = disjoint_union( - operator_vertex_suffix => operator, - bra_vertex_suffix => dag(bra_mapped), - ket_vertex_suffix => ket, - ) - return BilinearFormNetwork( - tn, operator_vertex_suffix, bra_vertex_suffix, ket_vertex_suffix - ) + operator::AbstractITensorNetwork, + bra::AbstractITensorNetwork, + ket::AbstractITensorNetwork; + operator_vertex_suffix = default_operator_vertex_suffix(), + bra_vertex_suffix = default_bra_vertex_suffix(), + ket_vertex_suffix = default_ket_vertex_suffix(), + dual_site_index_map = default_dual_site_index_map, + dual_link_index_map = default_dual_link_index_map, + ) + bra_mapped = dual_link_index_map(dual_site_index_map(bra; links = []); sites = []) + tn = disjoint_union( + operator_vertex_suffix => operator, + bra_vertex_suffix => dag(bra_mapped), + ket_vertex_suffix => ket, + ) + return BilinearFormNetwork( + tn, operator_vertex_suffix, bra_vertex_suffix, ket_vertex_suffix + ) end operator_vertex_suffix(blf::BilinearFormNetwork) = blf.operator_vertex_suffix @@ -46,55 +46,55 @@ ket_vertex_suffix(blf::BilinearFormNetwork) = blf.ket_vertex_suffix tensornetwork(blf::BilinearFormNetwork) = blf.tensornetwork function Base.copy(blf::BilinearFormNetwork) - return BilinearFormNetwork( - copy(tensornetwork(blf)), - operator_vertex_suffix(blf), - bra_vertex_suffix(blf), - ket_vertex_suffix(blf), - ) + return BilinearFormNetwork( + copy(tensornetwork(blf)), + operator_vertex_suffix(blf), + bra_vertex_suffix(blf), + ket_vertex_suffix(blf), + ) end function itensor_identity_map(elt::Type, i_pairs::Vector) - return prod(i_pairs; init=ITensor(one(Bool))) do i_pair - return denseblocks(delta(elt, last(i_pair), dag(first(i_pair)))) - end + return prod(i_pairs; init = ITensor(one(Bool))) do i_pair + return denseblocks(delta(elt, last(i_pair), dag(first(i_pair)))) + end end itensor_identity_map(i_pairs::Vector) = itensor_identity_map(Float64, i_pairs) function BilinearFormNetwork( - bra::AbstractITensorNetwork, - ket::AbstractITensorNetwork; - dual_site_index_map=default_dual_site_index_map, - kwargs..., -) - @assert issetequal(flatten_siteinds(bra), flatten_siteinds(ket)) - link_space = isempty(flatten_siteinds(bra)) ? 1 : nothing - s = siteinds(ket) - s_mapped = dual_site_index_map(s) - operator_inds = union_all_inds(s, s_mapped) + bra::AbstractITensorNetwork, + ket::AbstractITensorNetwork; + dual_site_index_map = default_dual_site_index_map, + kwargs..., + ) + @assert issetequal(flatten_siteinds(bra), flatten_siteinds(ket)) + link_space = isempty(flatten_siteinds(bra)) ? 1 : nothing + s = siteinds(ket) + s_mapped = dual_site_index_map(s) + operator_inds = union_all_inds(s, s_mapped) - O = ITensorNetwork(operator_inds; link_space) do v - return inds -> itensor_identity_map(scalartype(ket), s[v] .=> s_mapped[v]) - end - O = adapt(promote_type(datatype(bra), datatype(ket)), O) - return BilinearFormNetwork(O, bra, ket; dual_site_index_map, kwargs...) + O = ITensorNetwork(operator_inds; link_space) do v + return inds -> itensor_identity_map(scalartype(ket), s[v] .=> s_mapped[v]) + end + O = adapt(promote_type(datatype(bra), datatype(ket)), O) + return BilinearFormNetwork(O, bra, ket; dual_site_index_map, kwargs...) end function update( - blf::BilinearFormNetwork, - original_bra_state_vertex, - original_ket_state_vertex, - bra_state::ITensor, - ket_state::ITensor, -) - blf = copy(blf) - # TODO: Maybe add a check that it really does preserve the graph. - setindex_preserve_graph!( - tensornetwork(blf), bra_state, bra_vertex(blf, original_bra_state_vertex) - ) - setindex_preserve_graph!( - tensornetwork(blf), ket_state, ket_vertex(blf, original_ket_state_vertex) - ) - return blf + blf::BilinearFormNetwork, + original_bra_state_vertex, + original_ket_state_vertex, + bra_state::ITensor, + ket_state::ITensor, + ) + blf = copy(blf) + # TODO: Maybe add a check that it really does preserve the graph. + setindex_preserve_graph!( + tensornetwork(blf), bra_state, bra_vertex(blf, original_bra_state_vertex) + ) + setindex_preserve_graph!( + tensornetwork(blf), ket_state, ket_vertex(blf, original_ket_state_vertex) + ) + return blf end diff --git a/src/formnetworks/linearformnetwork.jl b/src/formnetworks/linearformnetwork.jl index 5e7cf1aa..c95cbdc1 100644 --- a/src/formnetworks/linearformnetwork.jl +++ b/src/formnetworks/linearformnetwork.jl @@ -3,33 +3,33 @@ using ITensors: ITensor, prime default_dual_link_index_map = prime struct LinearFormNetwork{ - V,TensorNetwork<:AbstractITensorNetwork{V},BraVertexSuffix,KetVertexSuffix -} <: AbstractFormNetwork{V} - tensornetwork::TensorNetwork - bra_vertex_suffix::BraVertexSuffix - ket_vertex_suffix::KetVertexSuffix + V, TensorNetwork <: AbstractITensorNetwork{V}, BraVertexSuffix, KetVertexSuffix, + } <: AbstractFormNetwork{V} + tensornetwork::TensorNetwork + bra_vertex_suffix::BraVertexSuffix + ket_vertex_suffix::KetVertexSuffix end function LinearFormNetwork( - bra::AbstractITensorNetwork, - ket::AbstractITensorNetwork; - bra_vertex_suffix=default_bra_vertex_suffix(), - ket_vertex_suffix=default_ket_vertex_suffix(), - dual_link_index_map=default_dual_link_index_map, -) - bra_mapped = dual_link_index_map(bra; sites=[]) - tn = disjoint_union(bra_vertex_suffix => dag(bra_mapped), ket_vertex_suffix => ket) - return LinearFormNetwork(tn, bra_vertex_suffix, ket_vertex_suffix) + bra::AbstractITensorNetwork, + ket::AbstractITensorNetwork; + bra_vertex_suffix = default_bra_vertex_suffix(), + ket_vertex_suffix = default_ket_vertex_suffix(), + dual_link_index_map = default_dual_link_index_map, + ) + bra_mapped = dual_link_index_map(bra; sites = []) + tn = disjoint_union(bra_vertex_suffix => dag(bra_mapped), ket_vertex_suffix => ket) + return LinearFormNetwork(tn, bra_vertex_suffix, ket_vertex_suffix) end function LinearFormNetwork(blf::BilinearFormNetwork) - bra, ket, operator = subgraph(blf, bra_vertices(blf)), - subgraph(blf, ket_vertices(blf)), - subgraph(blf, operator_vertices(blf)) - bra_suffix, ket_suffix = bra_vertex_suffix(blf), ket_vertex_suffix(blf) - operator = rename_vertices(v -> bra_vertex_map(blf)(v), operator) - tn = union(bra, ket, operator) - return LinearFormNetwork(tn, bra_suffix, ket_suffix) + bra, ket, operator = subgraph(blf, bra_vertices(blf)), + subgraph(blf, ket_vertices(blf)), + subgraph(blf, operator_vertices(blf)) + bra_suffix, ket_suffix = bra_vertex_suffix(blf), ket_vertex_suffix(blf) + operator = rename_vertices(v -> bra_vertex_map(blf)(v), operator) + tn = union(bra, ket, operator) + return LinearFormNetwork(tn, bra_suffix, ket_suffix) end bra_vertex_suffix(lf::LinearFormNetwork) = lf.bra_vertex_suffix @@ -38,16 +38,16 @@ ket_vertex_suffix(lf::LinearFormNetwork) = lf.ket_vertex_suffix tensornetwork(lf::LinearFormNetwork) = lf.tensornetwork function Base.copy(lf::LinearFormNetwork) - return LinearFormNetwork( - copy(tensornetwork(lf)), bra_vertex_suffix(lf), ket_vertex_suffix(lf) - ) + return LinearFormNetwork( + copy(tensornetwork(lf)), bra_vertex_suffix(lf), ket_vertex_suffix(lf) + ) end function update(lf::LinearFormNetwork, original_ket_state_vertex, ket_state::ITensor) - lf = copy(lf) - # TODO: Maybe add a check that it really does preserve the graph. - setindex_preserve_graph!( - tensornetwork(lf), ket_state, ket_vertex(blf, original_ket_state_vertex) - ) - return lf + lf = copy(lf) + # TODO: Maybe add a check that it really does preserve the graph. + setindex_preserve_graph!( + tensornetwork(lf), ket_state, ket_vertex(blf, original_ket_state_vertex) + ) + return lf end diff --git a/src/formnetworks/quadraticformnetwork.jl b/src/formnetworks/quadraticformnetwork.jl index c8150fe9..1f0917dd 100644 --- a/src/formnetworks/quadraticformnetwork.jl +++ b/src/formnetworks/quadraticformnetwork.jl @@ -1,82 +1,82 @@ default_index_map = prime default_inv_index_map = noprime -struct QuadraticFormNetwork{V,FormNetwork<:BilinearFormNetwork{V},IndexMap,InvIndexMap} <: - AbstractFormNetwork{V} - formnetwork::FormNetwork - dual_index_map::IndexMap - dual_inv_index_map::InvIndexMap +struct QuadraticFormNetwork{V, FormNetwork <: BilinearFormNetwork{V}, IndexMap, InvIndexMap} <: + AbstractFormNetwork{V} + formnetwork::FormNetwork + dual_index_map::IndexMap + dual_inv_index_map::InvIndexMap end bilinear_formnetwork(qf::QuadraticFormNetwork) = qf.formnetwork #Needed for implementation, forward from bilinear form for f in [ - :operator_vertex_suffix, - :bra_vertex_suffix, - :ket_vertex_suffix, - :tensornetwork, - :data_graph, - :data_graph_type, -] - @eval begin - function $f(qf::QuadraticFormNetwork, args...; kwargs...) - return $f(bilinear_formnetwork(qf), args...; kwargs...) + :operator_vertex_suffix, + :bra_vertex_suffix, + :ket_vertex_suffix, + :tensornetwork, + :data_graph, + :data_graph_type, + ] + @eval begin + function $f(qf::QuadraticFormNetwork, args...; kwargs...) + return $f(bilinear_formnetwork(qf), args...; kwargs...) + end end - end end dual_index_map(qf::QuadraticFormNetwork) = qf.dual_index_map dual_inv_index_map(qf::QuadraticFormNetwork) = qf.dual_inv_index_map function Base.copy(qf::QuadraticFormNetwork) - return QuadraticFormNetwork( - copy(bilinear_formnetwork(qf)), dual_index_map(qf), dual_inv_index_map(qf) - ) + return QuadraticFormNetwork( + copy(bilinear_formnetwork(qf)), dual_index_map(qf), dual_inv_index_map(qf) + ) end function QuadraticFormNetwork( - operator::AbstractITensorNetwork, - ket::AbstractITensorNetwork; - dual_index_map=default_index_map, - dual_inv_index_map=default_inv_index_map, - kwargs..., -) - blf = BilinearFormNetwork( - operator, - ket, - ket; - dual_site_index_map=dual_index_map, - dual_link_index_map=dual_index_map, - kwargs..., - ) - return QuadraticFormNetwork(blf, dual_index_map, dual_inv_index_map) + operator::AbstractITensorNetwork, + ket::AbstractITensorNetwork; + dual_index_map = default_index_map, + dual_inv_index_map = default_inv_index_map, + kwargs..., + ) + blf = BilinearFormNetwork( + operator, + ket, + ket; + dual_site_index_map = dual_index_map, + dual_link_index_map = dual_index_map, + kwargs..., + ) + return QuadraticFormNetwork(blf, dual_index_map, dual_inv_index_map) end function QuadraticFormNetwork( - ket::AbstractITensorNetwork; - dual_index_map=default_index_map, - dual_inv_index_map=default_inv_index_map, - kwargs..., -) - blf = BilinearFormNetwork( - ket, - ket; - dual_site_index_map=dual_index_map, - dual_link_index_map=dual_index_map, - kwargs..., - ) - return QuadraticFormNetwork(blf, dual_index_map, dual_inv_index_map) + ket::AbstractITensorNetwork; + dual_index_map = default_index_map, + dual_inv_index_map = default_inv_index_map, + kwargs..., + ) + blf = BilinearFormNetwork( + ket, + ket; + dual_site_index_map = dual_index_map, + dual_link_index_map = dual_index_map, + kwargs..., + ) + return QuadraticFormNetwork(blf, dual_index_map, dual_inv_index_map) end function update(qf::QuadraticFormNetwork, original_state_vertex, ket_state::ITensor) - state_inds = inds(ket_state) - bra_state = replaceinds(dag(ket_state), state_inds, dual_index_map(qf).(state_inds)) - new_blf = update( - bilinear_formnetwork(qf), - original_state_vertex, - original_state_vertex, - bra_state, - ket_state, - ) - return QuadraticFormNetwork(new_blf, dual_index_map(qf), dual_index_map(qf)) + state_inds = inds(ket_state) + bra_state = replaceinds(dag(ket_state), state_inds, dual_index_map(qf).(state_inds)) + new_blf = update( + bilinear_formnetwork(qf), + original_state_vertex, + original_state_vertex, + bra_state, + ket_state, + ) + return QuadraticFormNetwork(new_blf, dual_index_map(qf), dual_index_map(qf)) end diff --git a/src/gauging.jl b/src/gauging.jl index d51842d9..00c200c4 100644 --- a/src/gauging.jl +++ b/src/gauging.jl @@ -3,14 +3,14 @@ using ITensors.NDTensors: dense, scalartype using NamedGraphs.PartitionedGraphs: partitionedge function default_bond_tensors(ψ::ITensorNetwork) - return DataGraph( - underlying_graph(ψ); vertex_data_eltype=Nothing, edge_data_eltype=ITensor - ) + return DataGraph( + underlying_graph(ψ); vertex_data_eltype = Nothing, edge_data_eltype = ITensor + ) end -struct VidalITensorNetwork{V,BTS} <: AbstractITensorNetwork{V} - itensornetwork::ITensorNetwork{V} - bond_tensors::BTS +struct VidalITensorNetwork{V, BTS} <: AbstractITensorNetwork{V} + itensornetwork::ITensorNetwork{V} + bond_tensors::BTS end site_tensors(ψ::VidalITensorNetwork) = ψ.itensornetwork @@ -18,167 +18,167 @@ bond_tensors(ψ::VidalITensorNetwork) = ψ.bond_tensors bond_tensor(ψ::VidalITensorNetwork, e) = bond_tensors(ψ)[e] function data_graph_type(TN::Type{<:VidalITensorNetwork}) - return data_graph_type(fieldtype(TN, :itensornetwork)) + return data_graph_type(fieldtype(TN, :itensornetwork)) end data_graph(ψ::VidalITensorNetwork) = data_graph(site_tensors(ψ)) function Base.copy(ψ::VidalITensorNetwork) - return VidalITensorNetwork(copy(site_tensors(ψ)), copy(bond_tensors(ψ))) + return VidalITensorNetwork(copy(site_tensors(ψ)), copy(bond_tensors(ψ))) end default_norm_cache(ψ::ITensorNetwork) = BeliefPropagationCache(QuadraticFormNetwork(ψ)) function ITensorNetwork( - ψ_vidal::VidalITensorNetwork; (cache!)=nothing, update_gauge=false, update_kwargs... -) - if update_gauge - ψ_vidal = update(ψ_vidal; update_kwargs...) - end - - ψ = copy(site_tensors(ψ_vidal)) - - for e in edges(ψ) - vsrc, vdst = src(e), dst(e) - root_S = ITensorsExtensions.sqrt_diag(bond_tensor(ψ_vidal, e)) - setindex_preserve_graph!(ψ, noprime(root_S * ψ[vsrc]), vsrc) - setindex_preserve_graph!(ψ, noprime(root_S * ψ[vdst]), vdst) - end + ψ_vidal::VidalITensorNetwork; (cache!) = nothing, update_gauge = false, update_kwargs... + ) + if update_gauge + ψ_vidal = update(ψ_vidal; update_kwargs...) + end - if !isnothing(cache!) - bp_cache = default_norm_cache(ψ) - mts = messages(bp_cache) + ψ = copy(site_tensors(ψ_vidal)) for e in edges(ψ) - vsrc, vdst = src(e), dst(e) - pe = partitionedge(bp_cache, (vsrc, "bra") => (vdst, "bra")) - set!(mts, pe, copy(ITensor[dense(bond_tensor(ψ_vidal, e))])) - set!(mts, reverse(pe), copy(ITensor[dense(bond_tensor(ψ_vidal, e))])) + vsrc, vdst = src(e), dst(e) + root_S = ITensorsExtensions.sqrt_diag(bond_tensor(ψ_vidal, e)) + setindex_preserve_graph!(ψ, noprime(root_S * ψ[vsrc]), vsrc) + setindex_preserve_graph!(ψ, noprime(root_S * ψ[vdst]), vdst) end - bp_cache = set_messages(bp_cache, mts) - cache![] = bp_cache - end + if !isnothing(cache!) + bp_cache = default_norm_cache(ψ) + mts = messages(bp_cache) + + for e in edges(ψ) + vsrc, vdst = src(e), dst(e) + pe = partitionedge(bp_cache, (vsrc, "bra") => (vdst, "bra")) + set!(mts, pe, copy(ITensor[dense(bond_tensor(ψ_vidal, e))])) + set!(mts, reverse(pe), copy(ITensor[dense(bond_tensor(ψ_vidal, e))])) + end - return ψ + bp_cache = set_messages(bp_cache, mts) + cache![] = bp_cache + end + + return ψ end """Use an ITensorNetwork ψ, its bond tensors and belief propagation cache to put ψ into the vidal gauge, return the bond tensors and updated_ψ.""" function vidalitensornetwork_preserve_cache( - ψ::ITensorNetwork; - cache=default_norm_cache(ψ), - bond_tensors=default_bond_tensors, - message_cutoff=10 * eps(real(scalartype(ψ))), - regularization=10 * eps(real(scalartype(ψ))), - edges=NamedGraphs.edges(ψ), - svd_kwargs..., -) - ψ_vidal_site_tensors = copy(ψ) - ψ_vidal_bond_tensors = bond_tensors(ψ) - - for e in edges - vsrc, vdst = src(e), dst(e) - ψvsrc, ψvdst = ψ_vidal_site_tensors[vsrc], ψ_vidal_site_tensors[vdst] - - pe = partitionedge(cache, (vsrc, "bra") => (vdst, "bra")) - edge_ind = commoninds(ψvsrc, ψvdst) - edge_ind_sim = sim(edge_ind) - - X_D, X_U = eigen(only(message(cache, pe)); ishermitian=true, cutoff=message_cutoff) - Y_D, Y_U = eigen( - only(message(cache, reverse(pe))); ishermitian=true, cutoff=message_cutoff + ψ::ITensorNetwork; + cache = default_norm_cache(ψ), + bond_tensors = default_bond_tensors, + message_cutoff = 10 * eps(real(scalartype(ψ))), + regularization = 10 * eps(real(scalartype(ψ))), + edges = NamedGraphs.edges(ψ), + svd_kwargs..., ) - X_D, Y_D = ITensorsExtensions.map_diag(x -> x + regularization, X_D), - ITensorsExtensions.map_diag(x -> x + regularization, Y_D) - - rootX_D, rootY_D = ITensorsExtensions.sqrt_diag(X_D), ITensorsExtensions.sqrt_diag(Y_D) - inv_rootX_D, inv_rootY_D = ITensorsExtensions.invsqrt_diag(X_D), - ITensorsExtensions.invsqrt_diag(Y_D) - rootX = X_U * rootX_D * prime(dag(X_U)) - rootY = Y_U * rootY_D * prime(dag(Y_U)) - inv_rootX = X_U * inv_rootX_D * prime(dag(X_U)) - inv_rootY = Y_U * inv_rootY_D * prime(dag(Y_U)) - - ψvsrc, ψvdst = noprime(ψvsrc * inv_rootX), noprime(ψvdst * inv_rootY) - - Ce = rootX - Ce = Ce * replaceinds(rootY, edge_ind, edge_ind_sim) - - U, S, V = svd(Ce, edge_ind; svd_kwargs...) - - new_edge_ind = Index[Index(dim(commoninds(S, U)), tags(first(edge_ind)))] - - ψvsrc = replaceinds(ψvsrc * U, commoninds(S, U), new_edge_ind) - ψvdst = replaceinds(ψvdst, edge_ind, edge_ind_sim) - ψvdst = replaceinds(ψvdst * V, commoninds(V, S), new_edge_ind) + ψ_vidal_site_tensors = copy(ψ) + ψ_vidal_bond_tensors = bond_tensors(ψ) + + for e in edges + vsrc, vdst = src(e), dst(e) + ψvsrc, ψvdst = ψ_vidal_site_tensors[vsrc], ψ_vidal_site_tensors[vdst] + + pe = partitionedge(cache, (vsrc, "bra") => (vdst, "bra")) + edge_ind = commoninds(ψvsrc, ψvdst) + edge_ind_sim = sim(edge_ind) + + X_D, X_U = eigen(only(message(cache, pe)); ishermitian = true, cutoff = message_cutoff) + Y_D, Y_U = eigen( + only(message(cache, reverse(pe))); ishermitian = true, cutoff = message_cutoff + ) + X_D, Y_D = ITensorsExtensions.map_diag(x -> x + regularization, X_D), + ITensorsExtensions.map_diag(x -> x + regularization, Y_D) + + rootX_D, rootY_D = ITensorsExtensions.sqrt_diag(X_D), ITensorsExtensions.sqrt_diag(Y_D) + inv_rootX_D, inv_rootY_D = ITensorsExtensions.invsqrt_diag(X_D), + ITensorsExtensions.invsqrt_diag(Y_D) + rootX = X_U * rootX_D * prime(dag(X_U)) + rootY = Y_U * rootY_D * prime(dag(Y_U)) + inv_rootX = X_U * inv_rootX_D * prime(dag(X_U)) + inv_rootY = Y_U * inv_rootY_D * prime(dag(Y_U)) + + ψvsrc, ψvdst = noprime(ψvsrc * inv_rootX), noprime(ψvdst * inv_rootY) + + Ce = rootX + Ce = Ce * replaceinds(rootY, edge_ind, edge_ind_sim) + + U, S, V = svd(Ce, edge_ind; svd_kwargs...) + + new_edge_ind = Index[Index(dim(commoninds(S, U)), tags(first(edge_ind)))] + + ψvsrc = replaceinds(ψvsrc * U, commoninds(S, U), new_edge_ind) + ψvdst = replaceinds(ψvdst, edge_ind, edge_ind_sim) + ψvdst = replaceinds(ψvdst * V, commoninds(V, S), new_edge_ind) + + setindex_preserve_graph!(ψ_vidal_site_tensors, ψvsrc, vsrc) + setindex_preserve_graph!(ψ_vidal_site_tensors, ψvdst, vdst) + + S = replaceinds( + S, + [commoninds(S, U)..., commoninds(S, V)...] => + [new_edge_ind..., prime(new_edge_ind)...], + ) + ψ_vidal_bond_tensors[e] = S + end - setindex_preserve_graph!(ψ_vidal_site_tensors, ψvsrc, vsrc) - setindex_preserve_graph!(ψ_vidal_site_tensors, ψvdst, vdst) + return VidalITensorNetwork(ψ_vidal_site_tensors, ψ_vidal_bond_tensors) +end - S = replaceinds( - S, - [commoninds(S, U)..., commoninds(S, V)...] => - [new_edge_ind..., prime(new_edge_ind)...], +function VidalITensorNetwork( + ψ::ITensorNetwork; + (cache!) = nothing, + update_cache = isnothing(cache!), + cache_update_kwargs = (;), + kwargs..., ) - ψ_vidal_bond_tensors[e] = S - end + if isnothing(cache!) + cache! = Ref(default_norm_cache(ψ)) + end - return VidalITensorNetwork(ψ_vidal_site_tensors, ψ_vidal_bond_tensors) -end + if update_cache + cache![] = update(cache![]; cache_update_kwargs...) + end -function VidalITensorNetwork( - ψ::ITensorNetwork; - (cache!)=nothing, - update_cache=isnothing(cache!), - cache_update_kwargs=(;), - kwargs..., -) - if isnothing(cache!) - cache! = Ref(default_norm_cache(ψ)) - end - - if update_cache - cache![] = update(cache![]; cache_update_kwargs...) - end - - return vidalitensornetwork_preserve_cache(ψ; cache=cache![], kwargs...) + return vidalitensornetwork_preserve_cache(ψ; cache = cache![], kwargs...) end function update(ψ::VidalITensorNetwork; kwargs...) - return VidalITensorNetwork(ITensorNetwork(ψ; update_gauge=false); kwargs...) + return VidalITensorNetwork(ITensorNetwork(ψ; update_gauge = false); kwargs...) end """Function to construct the 'isometry' of a state in the Vidal Gauge on the given edge""" function vidal_gauge_isometry(ψ::VidalITensorNetwork, edge) - vsrc, vdst = src(edge), dst(edge) - ψ_vsrc = copy(ψ[vsrc]) + vsrc, vdst = src(edge), dst(edge) + ψ_vsrc = copy(ψ[vsrc]) - for vn in setdiff(neighbors(ψ, vsrc), [vdst]) - ψ_vsrc = noprime(ψ_vsrc * bond_tensor(ψ, vn => vsrc)) - end + for vn in setdiff(neighbors(ψ, vsrc), [vdst]) + ψ_vsrc = noprime(ψ_vsrc * bond_tensor(ψ, vn => vsrc)) + end - ψ_vsrcdag = dag(ψ_vsrc) - ψ_vsrcdag = replaceind(ψ_vsrcdag, commonind(ψ_vsrc, ψ[vdst]), commonind(ψ_vsrc, ψ[vdst])') + ψ_vsrcdag = dag(ψ_vsrc) + ψ_vsrcdag = replaceind(ψ_vsrcdag, commonind(ψ_vsrc, ψ[vdst]), commonind(ψ_vsrc, ψ[vdst])') - return ψ_vsrcdag * ψ_vsrc + return ψ_vsrcdag * ψ_vsrc end function vidal_gauge_isometries(ψ::VidalITensorNetwork, edges::Vector) - return Dict([e => vidal_gauge_isometry(ψ, e) for e in edges]) + return Dict([e => vidal_gauge_isometry(ψ, e) for e in edges]) end function vidal_gauge_isometries(ψ::VidalITensorNetwork) - return vidal_gauge_isometries( - ψ, vcat(NamedGraphs.edges(ψ), reverse.(NamedGraphs.edges(ψ))) - ) + return vidal_gauge_isometries( + ψ, vcat(NamedGraphs.edges(ψ), reverse.(NamedGraphs.edges(ψ))) + ) end """Function to measure the 'distance' of a state from the Vidal Gauge""" function gauge_error(ψ::VidalITensorNetwork) - f = 0 - isometries = vidal_gauge_isometries(ψ) - for e in keys(isometries) - lhs = isometries[e] - f += message_diff(ITensor[lhs], ITensor[denseblocks(delta(inds(lhs)))]) - end - - return f / (length(keys(isometries))) + f = 0 + isometries = vidal_gauge_isometries(ψ) + for e in keys(isometries) + lhs = isometries[e] + f += message_diff(ITensor[lhs], ITensor[denseblocks(delta(inds(lhs)))]) + end + + return f / (length(keys(isometries))) end diff --git a/src/graphs.jl b/src/graphs.jl index bce2c90d..6465b360 100644 --- a/src/graphs.jl +++ b/src/graphs.jl @@ -2,12 +2,12 @@ using Graphs.SimpleGraphs: SimpleGraphs, SimpleGraph using ITensors: ITensor, hascommoninds function SimpleGraphs.SimpleGraph(itensors::Vector{ITensor}) - nv_graph = length(itensors) - graph = SimpleGraph(nv_graph) - for i in 1:(nv_graph - 1), j in (i + 1):nv_graph - if hascommoninds(itensors[i], itensors[j]) - add_edge!(graph, i => j) + nv_graph = length(itensors) + graph = SimpleGraph(nv_graph) + for i in 1:(nv_graph - 1), j in (i + 1):nv_graph + if hascommoninds(itensors[i], itensors[j]) + add_edge!(graph, i => j) + end end - end - return graph + return graph end diff --git a/src/indextags.jl b/src/indextags.jl index 1ca3fdf1..187eb932 100644 --- a/src/indextags.jl +++ b/src/indextags.jl @@ -4,13 +4,13 @@ vertex_tag(v) = replace("$v", "," => "×", "(" => "", ")" => "") edge_tag(e::Pair) = edge_tag(NamedEdge(e)) function edge_tag(e) - return "$(vertex_tag(src(e))),$(vertex_tag(dst(e)))" + return "$(vertex_tag(src(e))),$(vertex_tag(dst(e)))" end function vertex_index(v, vertex_space) - return Index(vertex_space; tags=vertex_tag(v)) + return Index(vertex_space; tags = vertex_tag(v)) end function edge_index(e, edge_space) - return Index(edge_space; tags=edge_tag(e)) + return Index(edge_space; tags = edge_tag(e)) end diff --git a/src/indsnetwork.jl b/src/indsnetwork.jl index ea36ab95..1344f139 100644 --- a/src/indsnetwork.jl +++ b/src/indsnetwork.jl @@ -9,14 +9,14 @@ using NamedGraphs.GraphsExtensions: vertextype using NamedGraphs.NamedGraphGenerators: named_path_graph using SimpleTraits: SimpleTraits, Not, @traitfn -struct IndsNetwork{V,I} <: AbstractIndsNetwork{V,I} - data_graph::DataGraph{V,Vector{I},Vector{I},NamedGraph{V},NamedEdge{V}} - global function _IndsNetwork(V::Type, I::Type, g::DataGraph) - return new{V,I}(g) - end +struct IndsNetwork{V, I} <: AbstractIndsNetwork{V, I} + data_graph::DataGraph{V, Vector{I}, Vector{I}, NamedGraph{V}, NamedEdge{V}} + global function _IndsNetwork(V::Type, I::Type, g::DataGraph) + return new{V, I}(g) + end end ITensorsExtensions.indtype(inds_network::IndsNetwork) = indtype(typeof(inds_network)) -ITensorsExtensions.indtype(::Type{<:IndsNetwork{V,I}}) where {V,I} = I +ITensorsExtensions.indtype(::Type{<:IndsNetwork{V, I}}) where {V, I} = I data_graph(is::IndsNetwork) = is.data_graph DataGraphs.underlying_graph(is::IndsNetwork) = underlying_graph(data_graph(is)) NamedGraphs.vertextype(::Type{<:IndsNetwork{V}}) where {V} = V @@ -30,258 +30,258 @@ Graphs.is_directed(::Type{<:IndsNetwork}) = false # When setting an edge with collections of `Index`, set the reverse direction # edge with the `dag`. function DataGraphs.reverse_data_direction( - inds_network::IndsNetwork, is::Union{Index,Tuple{Vararg{Index}},Vector{<:Index}} -) - return dag(is) + inds_network::IndsNetwork, is::Union{Index, Tuple{Vararg{Index}}, Vector{<:Index}} + ) + return dag(is) end function IndsNetwork{V}(data_graph::DataGraph) where {V} - I = eltype(eltype(vertex_data(data_graph))) - return IndsNetwork{V,I}(data_graph) + I = eltype(eltype(vertex_data(data_graph))) + return IndsNetwork{V, I}(data_graph) end function IndsNetwork(data_graph::DataGraph) - return IndsNetwork{vertextype(data_graph)}(data_graph) + return IndsNetwork{vertextype(data_graph)}(data_graph) end -@traitfn function IndsNetwork{V,I}(g::G) where {G<:DataGraph,V,I;!IsUnderlyingGraph{G}} - return _IndsNetwork(V, I, g) +@traitfn function IndsNetwork{V, I}(g::G) where {G <: DataGraph, V, I; !IsUnderlyingGraph{G}} + return _IndsNetwork(V, I, g) end -function IndsNetwork{V,I}(g::AbstractNamedGraph, link_space, site_space) where {V,I} - link_space_dictionary = link_space_map(V, I, g, link_space) - site_space_dictionary = site_space_map(V, I, g, site_space) - return IndsNetwork{V,I}(g, link_space_dictionary, site_space_dictionary) +function IndsNetwork{V, I}(g::AbstractNamedGraph, link_space, site_space) where {V, I} + link_space_dictionary = link_space_map(V, I, g, link_space) + site_space_dictionary = site_space_map(V, I, g, site_space) + return IndsNetwork{V, I}(g, link_space_dictionary, site_space_dictionary) end -function IndsNetwork{V,I}(g::AbstractSimpleGraph, link_space, site_space) where {V,I} - return IndsNetwork{V,I}(NamedGraph(g), link_space, site_space) +function IndsNetwork{V, I}(g::AbstractSimpleGraph, link_space, site_space) where {V, I} + return IndsNetwork{V, I}(NamedGraph(g), link_space, site_space) end @traitfn function IndsNetwork{V}( - g::G, link_space, site_space -) where {G,V;IsUnderlyingGraph{G}} - I = indtype(link_space, site_space) - return IndsNetwork{V,I}(g, link_space, site_space) + g::G, link_space, site_space + ) where {G, V; IsUnderlyingGraph{G}} + I = indtype(link_space, site_space) + return IndsNetwork{V, I}(g, link_space, site_space) end -@traitfn function IndsNetwork(g::G, link_space, site_space) where {G;IsUnderlyingGraph{G}} - V = vertextype(g) - return IndsNetwork{V}(g, link_space, site_space) +@traitfn function IndsNetwork(g::G, link_space, site_space) where {{G; IsUnderlyingGraph{G}}} + V = vertextype(g) + return IndsNetwork{V}(g, link_space, site_space) end # Core constructor, others should convert # their inputs to these types. # TODO: Make this an inner constructor `_IndsNetwork`? -function IndsNetwork{V,I}( - g::AbstractNamedGraph, - link_space::Dictionary{<:Any,<:Vector{<:Index}}, - site_space::Dictionary{<:Any,<:Vector{<:Index}}, -) where {V,I} - dg = DataGraph{V}(g; vertex_data_eltype=Vector{I}, edge_data_eltype=Vector{I}) - for e in keys(link_space) - dg[e] = link_space[e] - end - for v in keys(site_space) - dg[v] = site_space[v] - end - return IndsNetwork{V}(dg) -end - -function IndsNetwork{V,I}( - g::AbstractSimpleGraph, - link_space::Dictionary{<:Any,<:Vector{<:Index}}, - site_space::Dictionary{<:Any,<:Vector{<:Index}}, -) where {V,I} - return IndsNetwork{V,I}(NamedGraph(g), link_space, site_space) +function IndsNetwork{V, I}( + g::AbstractNamedGraph, + link_space::Dictionary{<:Any, <:Vector{<:Index}}, + site_space::Dictionary{<:Any, <:Vector{<:Index}}, + ) where {V, I} + dg = DataGraph{V}(g; vertex_data_eltype = Vector{I}, edge_data_eltype = Vector{I}) + for e in keys(link_space) + dg[e] = link_space[e] + end + for v in keys(site_space) + dg[v] = site_space[v] + end + return IndsNetwork{V}(dg) +end + +function IndsNetwork{V, I}( + g::AbstractSimpleGraph, + link_space::Dictionary{<:Any, <:Vector{<:Index}}, + site_space::Dictionary{<:Any, <:Vector{<:Index}}, + ) where {V, I} + return IndsNetwork{V, I}(NamedGraph(g), link_space, site_space) end # Construct from collections of indices function path_indsnetwork(external_inds::Vector{<:Vector{<:Index}}) - g = named_path_graph(length(external_inds)) - is = IndsNetwork(g) - for v in eachindex(external_inds) - is[v] = external_inds[v] - end - return is + g = named_path_graph(length(external_inds)) + is = IndsNetwork(g) + for v in eachindex(external_inds) + is[v] = external_inds[v] + end + return is end function path_indsnetwork(external_inds::Vector{<:Index}) - return path_indsnetwork(map(i -> [i], external_inds)) + return path_indsnetwork(map(i -> [i], external_inds)) end @traitfn function default_link_space(V::Type, g::::IsUnderlyingGraph) - # TODO: Convert `g` to vertex type `V` - E = edgetype(g) - return Dictionary{E,Vector{Index}}() + # TODO: Convert `g` to vertex type `V` + E = edgetype(g) + return Dictionary{E, Vector{Index}}() end @traitfn function default_site_space(V::Type, g::::IsUnderlyingGraph) - return Dictionary{V,Vector{Index}}() + return Dictionary{V, Vector{Index}}() end -@traitfn function IndsNetwork{V,I}( - g::G; link_space=nothing, site_space=nothing -) where {G,V,I;IsUnderlyingGraph{G}} - return IndsNetwork{V,I}(g, link_space, site_space) +@traitfn function IndsNetwork{V, I}( + g::G; link_space = nothing, site_space = nothing + ) where {G, V, I; IsUnderlyingGraph{G}} + return IndsNetwork{V, I}(g, link_space, site_space) end @traitfn function IndsNetwork{V}( - g::G; link_space=nothing, site_space=nothing -) where {G,V;IsUnderlyingGraph{G}} - return IndsNetwork{V}(g, link_space, site_space) + g::G; link_space = nothing, site_space = nothing + ) where {G, V; IsUnderlyingGraph{G}} + return IndsNetwork{V}(g, link_space, site_space) end -@traitfn function IndsNetwork(g::G; kwargs...) where {G;IsUnderlyingGraph{G}} - return IndsNetwork{vertextype(g)}(g; kwargs...) +@traitfn function IndsNetwork(g::G; kwargs...) where {{G; IsUnderlyingGraph{G}}} + return IndsNetwork{vertextype(g)}(g; kwargs...) end @traitfn function link_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - link_spaces::Dictionary{<:Any,Vector{Int}}, -) - # TODO: Convert `g` to vertex type `V` - # @assert vertextype(g) == V - E = edgetype(g) - linkinds_dictionary = Dictionary{E,Vector{I}}() - for e in keys(link_spaces) - l = [edge_index(e, link_space) for link_space in link_spaces[e]] - set!(linkinds_dictionary, e, l) - # set!(linkinds_dictionary, reverse(e), dag(l)) - end - return linkinds_dictionary + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + link_spaces::Dictionary{<:Any, Vector{Int}}, + ) + # TODO: Convert `g` to vertex type `V` + # @assert vertextype(g) == V + E = edgetype(g) + linkinds_dictionary = Dictionary{E, Vector{I}}() + for e in keys(link_spaces) + l = [edge_index(e, link_space) for link_space in link_spaces[e]] + set!(linkinds_dictionary, e, l) + # set!(linkinds_dictionary, reverse(e), dag(l)) + end + return linkinds_dictionary end @traitfn function link_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - linkinds::AbstractDictionary{<:Any,Vector{<:Index}}, -) - E = edgetype(g) - return convert(Dictionary{E,Vector{I}}, linkinds) + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + linkinds::AbstractDictionary{<:Any, Vector{<:Index}}, + ) + E = edgetype(g) + return convert(Dictionary{E, Vector{I}}, linkinds) end @traitfn function link_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - linkinds::AbstractDictionary{<:Any,<:Index}, -) - return link_space_map(V, I, g, map(l -> [l], linkinds)) + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + linkinds::AbstractDictionary{<:Any, <:Index}, + ) + return link_space_map(V, I, g, map(l -> [l], linkinds)) end @traitfn function link_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_spaces::Dictionary{<:Any,Int} -) - return link_space_map(V, I, g, map(link_space -> [link_space], link_spaces)) + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_spaces::Dictionary{<:Any, Int} + ) + return link_space_map(V, I, g, map(link_space -> [link_space], link_spaces)) end @traitfn function link_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_spaces::Vector{Int} -) - return link_space_map(V, I, g, map(Returns(link_spaces), Indices(edges(g)))) + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_spaces::Vector{Int} + ) + return link_space_map(V, I, g, map(Returns(link_spaces), Indices(edges(g)))) end # TODO: Generalize using `IsIndexSpace` trait that is true for # `Integer` and `Vector{<:Pair{QN,<:Integer}}`. @traitfn function link_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_space::Int -) - return link_space_map(V, I, g, [link_space]) + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_space::Int + ) + return link_space_map(V, I, g, [link_space]) end @traitfn function link_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_space::Nothing -) - # TODO: Make sure `edgetype(g)` is consistent with vertex type `V` - return Dictionary{edgetype(g),Vector{I}}() + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, link_space::Nothing + ) + # TODO: Make sure `edgetype(g)` is consistent with vertex type `V` + return Dictionary{edgetype(g), Vector{I}}() end # TODO: Convert the dictionary according to `V` and `I` @traitfn function link_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - link_space::AbstractDictionary{<:Any,<:Vector{<:Index}}, -) - return link_space + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + link_space::AbstractDictionary{<:Any, <:Vector{<:Index}}, + ) + return link_space end @traitfn function site_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - siteinds::AbstractDictionary{<:Any,Vector{<:Index}}, -) - return convert(Dictionary{V,Vector{I}}, siteinds) + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + siteinds::AbstractDictionary{<:Any, Vector{<:Index}}, + ) + return convert(Dictionary{V, Vector{I}}, siteinds) end @traitfn function site_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - siteinds::AbstractDictionary{<:Any,<:Index}, -) - return site_space_map(V, I, g, map(s -> [s], siteinds)) + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + siteinds::AbstractDictionary{<:Any, <:Index}, + ) + return site_space_map(V, I, g, map(s -> [s], siteinds)) end @traitfn function site_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - site_spaces::AbstractDictionary{<:Any,Vector{Int}}, -) - siteinds_dictionary = Dictionary{V,Vector{I}}() - for v in keys(site_spaces) - s = [vertex_index(v, site_space) for site_space in site_spaces[v]] - set!(siteinds_dictionary, v, s) - end - return siteinds_dictionary + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + site_spaces::AbstractDictionary{<:Any, Vector{Int}}, + ) + siteinds_dictionary = Dictionary{V, Vector{I}}() + for v in keys(site_spaces) + s = [vertex_index(v, site_space) for site_space in site_spaces[v]] + set!(siteinds_dictionary, v, s) + end + return siteinds_dictionary end # TODO: Generalize using `IsIndexSpace` trait that is true for # `Integer` and `Vector{<:Pair{QN,<:Integer}}`. @traitfn function site_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, site_space::Int -) - return site_space_map(V, I, g, [site_space]) + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, site_space::Int + ) + return site_space_map(V, I, g, [site_space]) end # Multiple site indices per vertex # TODO: How to distinguish from block indices? @traitfn function site_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, site_spaces::Vector{Int} -) - return site_space_map(V, I, g, map(Returns(site_spaces), Indices(vertices(g)))) + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, site_spaces::Vector{Int} + ) + return site_space_map(V, I, g, map(Returns(site_spaces), Indices(vertices(g)))) end @traitfn function site_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - site_spaces::AbstractDictionary{<:Any,Int}, -) - return site_space_map(V, I, g, map(site_space -> [site_space], site_spaces)) + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + site_spaces::AbstractDictionary{<:Any, Int}, + ) + return site_space_map(V, I, g, map(site_space -> [site_space], site_spaces)) end # TODO: Convert the dictionary according to `V` and `I` @traitfn function site_space_map( - V::Type, - I::Type{<:Index}, - g::::IsUnderlyingGraph, - site_space::AbstractDictionary{<:Any,<:Vector{<:Index}}, -) - return site_space + V::Type, + I::Type{<:Index}, + g::::IsUnderlyingGraph, + site_space::AbstractDictionary{<:Any, <:Vector{<:Index}}, + ) + return site_space end @traitfn function site_space_map( - V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, site_space::Nothing -) - return Dictionary{V,Vector{I}}() + V::Type, I::Type{<:Index}, g::::IsUnderlyingGraph, site_space::Nothing + ) + return Dictionary{V, Vector{I}}() end # @@ -290,8 +290,8 @@ end Base.copy(is::IndsNetwork) = IndsNetwork(copy(data_graph(is))) -function map_inds(f, is::IndsNetwork, args...; sites=nothing, links=nothing, kwargs...) - return map_data(i -> f(i, args...; kwargs...), is; vertices=sites, edges=links) +function map_inds(f, is::IndsNetwork, args...; sites = nothing, links = nothing, kwargs...) + return map_data(i -> f(i, args...; kwargs...), is; vertices = sites, edges = links) end # @@ -303,5 +303,5 @@ end # based on `ITensorVisualizationCore`.). using ITensors.ITensorVisualizationCore: ITensorVisualizationCore, visualize function ITensorVisualizationCore.visualize(is::IndsNetwork, args...; kwargs...) - return visualize(ITensorNetwork(is), args...; kwargs...) + return visualize(ITensorNetwork(is), args...; kwargs...) end diff --git a/src/inner.jl b/src/inner.jl index 70bced01..c1ff3c63 100644 --- a/src/inner.jl +++ b/src/inner.jl @@ -4,134 +4,134 @@ using LinearAlgebra: norm, norm_sqr default_contract_alg(tns::Tuple) = "bp" function ITensors.inner( - ϕ::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - alg=default_contract_alg((ϕ, ψ)), - kwargs..., -) - return inner(Algorithm(alg), ϕ, ψ; kwargs...) + ϕ::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + alg = default_contract_alg((ϕ, ψ)), + kwargs..., + ) + return inner(Algorithm(alg), ϕ, ψ; kwargs...) end function ITensors.inner( - ϕ::AbstractITensorNetwork, - A::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - alg=default_contract_alg((ϕ, A, ψ)), - kwargs..., -) - return inner(Algorithm(alg), ϕ, A, ψ; kwargs...) + ϕ::AbstractITensorNetwork, + A::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + alg = default_contract_alg((ϕ, A, ψ)), + kwargs..., + ) + return inner(Algorithm(alg), ϕ, A, ψ; kwargs...) end function ITensors.inner( - alg::Algorithm"exact", - ϕ::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - sequence=nothing, - contraction_sequence_kwargs=(;), - kwargs..., -) - tn = inner_network(ϕ, ψ; kwargs...) - if isnothing(sequence) - sequence = contraction_sequence(tn; contraction_sequence_kwargs...) - end - return scalar(tn; sequence) + alg::Algorithm"exact", + ϕ::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + sequence = nothing, + contraction_sequence_kwargs = (;), + kwargs..., + ) + tn = inner_network(ϕ, ψ; kwargs...) + if isnothing(sequence) + sequence = contraction_sequence(tn; contraction_sequence_kwargs...) + end + return scalar(tn; sequence) end function ITensors.inner( - alg::Algorithm"exact", - ϕ::AbstractITensorNetwork, - A::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - sequence=nothing, - contraction_sequence_kwargs=(;), - kwargs..., -) - tn = inner_network(ϕ, A, ψ; kwargs...) - if isnothing(sequence) - sequence = contraction_sequence(tn; contraction_sequence_kwargs...) - end - return scalar(tn; sequence) + alg::Algorithm"exact", + ϕ::AbstractITensorNetwork, + A::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + sequence = nothing, + contraction_sequence_kwargs = (;), + kwargs..., + ) + tn = inner_network(ϕ, A, ψ; kwargs...) + if isnothing(sequence) + sequence = contraction_sequence(tn; contraction_sequence_kwargs...) + end + return scalar(tn; sequence) end function loginner( - ϕ::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - alg=default_contract_alg((ϕ, ψ)), - kwargs..., -) - return loginner(Algorithm(alg), ϕ, ψ; kwargs...) + ϕ::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + alg = default_contract_alg((ϕ, ψ)), + kwargs..., + ) + return loginner(Algorithm(alg), ϕ, ψ; kwargs...) end function loginner( - ϕ::AbstractITensorNetwork, - A::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - alg=default_contract_alg((ϕ, A, ψ)), - kwargs..., -) - return loginner(Algorithm(alg), ϕ, A, ψ; kwargs...) + ϕ::AbstractITensorNetwork, + A::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + alg = default_contract_alg((ϕ, A, ψ)), + kwargs..., + ) + return loginner(Algorithm(alg), ϕ, A, ψ; kwargs...) end function loginner( - alg::Algorithm"exact", ϕ::AbstractITensorNetwork, ψ::AbstractITensorNetwork; kwargs... -) - return log(inner(alg, ϕ, ψ); kwargs...) + alg::Algorithm"exact", ϕ::AbstractITensorNetwork, ψ::AbstractITensorNetwork; kwargs... + ) + return log(inner(alg, ϕ, ψ); kwargs...) end function loginner( - alg::Algorithm"exact", - ϕ::AbstractITensorNetwork, - A::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - kwargs..., -) - return log(inner(alg, ϕ, A, ψ); kwargs...) + alg::Algorithm"exact", + ϕ::AbstractITensorNetwork, + A::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + kwargs..., + ) + return log(inner(alg, ϕ, A, ψ); kwargs...) end function loginner( - alg::Algorithm, - ϕ::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - dual_link_index_map=sim, - kwargs..., -) - tn = inner_network(ϕ, ψ; dual_link_index_map) - return logscalar(alg, tn; kwargs...) + alg::Algorithm, + ϕ::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + dual_link_index_map = sim, + kwargs..., + ) + tn = inner_network(ϕ, ψ; dual_link_index_map) + return logscalar(alg, tn; kwargs...) end function loginner( - alg::Algorithm, - ϕ::AbstractITensorNetwork, - A::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - dual_link_index_map=sim, - kwargs..., -) - tn = inner_network(ϕ, A, ψ; dual_link_index_map) - return logscalar(alg, tn; kwargs...) + alg::Algorithm, + ϕ::AbstractITensorNetwork, + A::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + dual_link_index_map = sim, + kwargs..., + ) + tn = inner_network(ϕ, A, ψ; dual_link_index_map) + return logscalar(alg, tn; kwargs...) end function ITensors.inner( - alg::Algorithm, - ϕ::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - dual_link_index_map=sim, - kwargs..., -) - tn = inner_network(ϕ, ψ; dual_link_index_map) - return scalar(alg, tn; kwargs...) + alg::Algorithm, + ϕ::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + dual_link_index_map = sim, + kwargs..., + ) + tn = inner_network(ϕ, ψ; dual_link_index_map) + return scalar(alg, tn; kwargs...) end function ITensors.inner( - alg::Algorithm, - ϕ::AbstractITensorNetwork, - A::AbstractITensorNetwork, - ψ::AbstractITensorNetwork; - dual_link_index_map=sim, - kwargs..., -) - tn = inner_network(ϕ, A, ψ; dual_link_index_map) - return scalar(alg, tn; kwargs...) + alg::Algorithm, + ϕ::AbstractITensorNetwork, + A::AbstractITensorNetwork, + ψ::AbstractITensorNetwork; + dual_link_index_map = sim, + kwargs..., + ) + tn = inner_network(ϕ, A, ψ; dual_link_index_map) + return scalar(alg, tn; kwargs...) end # TODO: rename `sqnorm` to match https://github.com/JuliaStats/Distances.jl, @@ -139,5 +139,5 @@ end LinearAlgebra.norm_sqr(ψ::AbstractITensorNetwork; kwargs...) = inner(ψ, ψ; kwargs...) function LinearAlgebra.norm(ψ::AbstractITensorNetwork; kwargs...) - return sqrt(abs(real(norm_sqr(ψ; kwargs...)))) + return sqrt(abs(real(norm_sqr(ψ; kwargs...)))) end diff --git a/src/itensornetwork.jl b/src/itensornetwork.jl index 6c3cf060..c436a378 100644 --- a/src/itensornetwork.jl +++ b/src/itensornetwork.jl @@ -10,10 +10,10 @@ struct Private end ITensorNetwork """ struct ITensorNetwork{V} <: AbstractITensorNetwork{V} - data_graph::DataGraph{V,ITensor,ITensor,NamedGraph{V},NamedEdge{V}} - global function _ITensorNetwork(data_graph::DataGraph) - return new{vertextype(data_graph)}(data_graph) - end + data_graph::DataGraph{V, ITensor, ITensor, NamedGraph{V}, NamedEdge{V}} + global function _ITensorNetwork(data_graph::DataGraph) + return new{vertextype(data_graph)}(data_graph) + end end # @@ -24,26 +24,26 @@ data_graph(tn::ITensorNetwork) = getfield(tn, :data_graph) data_graph_type(TN::Type{<:ITensorNetwork}) = fieldtype(TN, :data_graph) function DataGraphs.underlying_graph_type(TN::Type{<:ITensorNetwork}) - return fieldtype(data_graph_type(TN), :underlying_graph) + return fieldtype(data_graph_type(TN), :underlying_graph) end # Versions taking vertex types. function ITensorNetwork{V}() where {V} - # TODO: Is there a better way to write this? - # Try using `convert_vertextype`. - new_data_graph_type = data_graph_type(ITensorNetwork{V}) - new_underlying_graph_type = underlying_graph_type(new_data_graph_type) - return _ITensorNetwork(new_data_graph_type(new_underlying_graph_type())) + # TODO: Is there a better way to write this? + # Try using `convert_vertextype`. + new_data_graph_type = data_graph_type(ITensorNetwork{V}) + new_underlying_graph_type = underlying_graph_type(new_data_graph_type) + return _ITensorNetwork(new_data_graph_type(new_underlying_graph_type())) end function ITensorNetwork{V}(tn::ITensorNetwork) where {V} - # TODO: Is there a better way to write this? - # Try using `convert_vertextype`. - return _ITensorNetwork(DataGraph{V}(data_graph(tn))) + # TODO: Is there a better way to write this? + # Try using `convert_vertextype`. + return _ITensorNetwork(DataGraph{V}(data_graph(tn))) end function ITensorNetwork{V}(g::NamedGraph) where {V} - # TODO: Is there a better way to write this? - # Try using `convert_vertextype`. - return ITensorNetwork(NamedGraph{V}(g)) + # TODO: Is there a better way to write this? + # Try using `convert_vertextype`. + return ITensorNetwork(NamedGraph{V}(g)) end ITensorNetwork() = ITensorNetwork{Any}() @@ -62,31 +62,31 @@ Base.copy(tn::ITensorNetwork) = _ITensorNetwork(copy(data_graph(tn))) # function itensors_to_itensornetwork(ts) - g = NamedGraph(collect(eachindex(ts))) - tn = ITensorNetwork(g) - for v in vertices(g) - tn[v] = ts[v] - end - return tn + g = NamedGraph(collect(eachindex(ts))) + tn = ITensorNetwork(g) + for v in vertices(g) + tn[v] = ts[v] + end + return tn end function ITensorNetwork(ts::AbstractVector{ITensor}) - return itensors_to_itensornetwork(ts) + return itensors_to_itensornetwork(ts) end -function ITensorNetwork(ts::AbstractDictionary{<:Any,ITensor}) - return itensors_to_itensornetwork(ts) +function ITensorNetwork(ts::AbstractDictionary{<:Any, ITensor}) + return itensors_to_itensornetwork(ts) end -function ITensorNetwork(ts::AbstractDict{<:Any,ITensor}) - return itensors_to_itensornetwork(ts) +function ITensorNetwork(ts::AbstractDict{<:Any, ITensor}) + return itensors_to_itensornetwork(ts) end function ITensorNetwork(vs::AbstractVector, ts::AbstractVector{ITensor}) - return itensors_to_itensornetwork(Dictionary(vs, ts)) + return itensors_to_itensornetwork(Dictionary(vs, ts)) end -function ITensorNetwork(ts::AbstractVector{<:Pair{<:Any,ITensor}}) - return itensors_to_itensornetwork(dictionary(ts)) +function ITensorNetwork(ts::AbstractVector{<:Pair{<:Any, ITensor}}) + return itensors_to_itensornetwork(dictionary(ts)) end # TODO: Decide what this should do, maybe it should factorize? function ITensorNetwork(t::ITensor) - return itensors_to_itensornetwork([t]) + return itensors_to_itensornetwork([t]) end # @@ -94,17 +94,17 @@ end # function ITensorNetwork( - eltype::Type, undef::UndefInitializer, graph::AbstractNamedGraph; kwargs... -) - return ITensorNetwork(eltype, undef, IndsNetwork(graph; kwargs...)) + eltype::Type, undef::UndefInitializer, graph::AbstractNamedGraph; kwargs... + ) + return ITensorNetwork(eltype, undef, IndsNetwork(graph; kwargs...)) end function ITensorNetwork(f, graph::AbstractNamedGraph; kwargs...) - return ITensorNetwork(f, IndsNetwork(graph; kwargs...)) + return ITensorNetwork(f, IndsNetwork(graph; kwargs...)) end function ITensorNetwork(graph::AbstractNamedGraph; kwargs...) - return ITensorNetwork(IndsNetwork(graph; kwargs...)) + return ITensorNetwork(IndsNetwork(graph; kwargs...)) end # @@ -112,17 +112,17 @@ end # function ITensorNetwork( - eltype::Type, undef::UndefInitializer, graph::AbstractSimpleGraph; kwargs... -) - return ITensorNetwork(eltype, undef, IndsNetwork(graph; kwargs...)) + eltype::Type, undef::UndefInitializer, graph::AbstractSimpleGraph; kwargs... + ) + return ITensorNetwork(eltype, undef, IndsNetwork(graph; kwargs...)) end function ITensorNetwork(f, graph::AbstractSimpleGraph; kwargs...) - return ITensorNetwork(f, IndsNetwork(graph); kwargs...) + return ITensorNetwork(f, IndsNetwork(graph); kwargs...) end function ITensorNetwork(graph::AbstractSimpleGraph; kwargs...) - return ITensorNetwork(IndsNetwork(graph); kwargs...) + return ITensorNetwork(IndsNetwork(graph); kwargs...) end # @@ -130,59 +130,59 @@ end # function ITensorNetwork(eltype::Type, undef::UndefInitializer, is::IndsNetwork; kwargs...) - return ITensorNetwork(is; kwargs...) do v - return (inds...) -> ITensor(eltype, undef, inds...) - end + return ITensorNetwork(is; kwargs...) do v + return (inds...) -> ITensor(eltype, undef, inds...) + end end function ITensorNetwork(eltype::Type, is::IndsNetwork; kwargs...) - return ITensorNetwork(is; kwargs...) do v - return (inds...) -> ITensor(eltype, inds...) - end + return ITensorNetwork(is; kwargs...) do v + return (inds...) -> ITensor(eltype, inds...) + end end function ITensorNetwork(undef::UndefInitializer, is::IndsNetwork; kwargs...) - return ITensorNetwork(is; kwargs...) do v - return (inds...) -> ITensor(undef, inds...) - end + return ITensorNetwork(is; kwargs...) do v + return (inds...) -> ITensor(undef, inds...) + end end function ITensorNetwork(is::IndsNetwork; kwargs...) - return ITensorNetwork(is; kwargs...) do v - return (inds...) -> ITensor(inds...) - end + return ITensorNetwork(is; kwargs...) do v + return (inds...) -> ITensor(inds...) + end end # TODO: Handle `eltype` and `undef` through `generic_state`. # `inds` are stored in a `NamedTuple` function generic_state(f, inds::NamedTuple) - return generic_state(f, reduce(vcat, inds.linkinds; init=inds.siteinds)) + return generic_state(f, reduce(vcat, inds.linkinds; init = inds.siteinds)) end function generic_state(f, inds::Vector) - return f(inds) + return f(inds) end function generic_state(a::AbstractArray, inds::Vector) - return itensor(a, inds) + return itensor(a, inds) end function generic_state(x::Op, inds::NamedTuple) - # TODO: Figure out what to do if there is more than one site. - if !isempty(inds.siteinds) - @assert length(inds.siteinds) == 2 - i = inds.siteinds[findfirst(i -> plev(i) == 0, inds.siteinds)] - @assert i' ∈ inds.siteinds - site_tensors = [op(x.which_op, i)] - else - site_tensors = [] - end - link_tensors = [[onehot(i => 1) for i in inds.linkinds[e]] for e in keys(inds.linkinds)] - return contract(reduce(vcat, link_tensors; init=site_tensors)) + # TODO: Figure out what to do if there is more than one site. + if !isempty(inds.siteinds) + @assert length(inds.siteinds) == 2 + i = inds.siteinds[findfirst(i -> plev(i) == 0, inds.siteinds)] + @assert i' ∈ inds.siteinds + site_tensors = [op(x.which_op, i)] + else + site_tensors = [] + end + link_tensors = [[onehot(i => 1) for i in inds.linkinds[e]] for e in keys(inds.linkinds)] + return contract(reduce(vcat, link_tensors; init = site_tensors)) end function generic_state(s::AbstractString, inds::NamedTuple) - # TODO: Figure out what to do if there is more than one site. - site_tensors = [state(s, only(inds.siteinds))] - link_tensors = [[onehot(i => 1) for i in inds.linkinds[e]] for e in keys(inds.linkinds)] - return contract(reduce(vcat, link_tensors; init=site_tensors)) + # TODO: Figure out what to do if there is more than one site. + site_tensors = [state(s, only(inds.siteinds))] + link_tensors = [[onehot(i => 1) for i in inds.linkinds[e]] for e in keys(inds.linkinds)] + return contract(reduce(vcat, link_tensors; init = site_tensors)) end # TODO: This is similar to `ModelHamiltonians.to_callable`, @@ -190,60 +190,60 @@ end to_callable(value::Type) = value to_callable(value::Function) = value function to_callable(value::AbstractDict) - return Base.Fix1(getindex, value) ∘ keytype(value) + return Base.Fix1(getindex, value) ∘ keytype(value) end function to_callable(value::AbstractDictionary) - return Base.Fix1(getindex, value) ∘ keytype(value) + return Base.Fix1(getindex, value) ∘ keytype(value) end -function to_callable(value::AbstractArray{<:Any,N}) where {N} - return Base.Fix1(getindex, value) ∘ CartesianIndex +function to_callable(value::AbstractArray{<:Any, N}) where {N} + return Base.Fix1(getindex, value) ∘ CartesianIndex end to_callable(value) = Returns(value) function ITensorNetwork(value, is::IndsNetwork; kwargs...) - return ITensorNetwork(to_callable(value), is; kwargs...) + return ITensorNetwork(to_callable(value), is; kwargs...) end function ITensorNetwork( - elt::Type, f, is::IndsNetwork; link_space=trivial_space(is), kwargs... -) - tn = ITensorNetwork(f, is; kwargs...) - for v in vertices(tn) - # TODO: Ideally we would use broadcasting, i.e. `elt.(tn[v])`, - # but that doesn't work right now on ITensors. - tn[v] = ITensors.convert_eltype(elt, tn[v]) - end - return tn + elt::Type, f, is::IndsNetwork; link_space = trivial_space(is), kwargs... + ) + tn = ITensorNetwork(f, is; kwargs...) + for v in vertices(tn) + # TODO: Ideally we would use broadcasting, i.e. `elt.(tn[v])`, + # but that doesn't work right now on ITensors. + tn[v] = ITensors.convert_eltype(elt, tn[v]) + end + return tn end function ITensorNetwork( - itensor_constructor::Function, is::IndsNetwork; link_space=trivial_space(is), kwargs... -) - is = insert_linkinds(is; link_space) - tn = ITensorNetwork{vertextype(is)}() - for v in vertices(is) - add_vertex!(tn, v) - end - for e in edges(is) - add_edge!(tn, e) - end - for v in vertices(tn) - # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. - siteinds = get(is, v, Index[]) - edges = [edgetype(is)(v, nv) for nv in neighbors(is, v)] - linkinds = map(e -> is[e], Indices(edges)) - tensor_v = generic_state(itensor_constructor(v), (; siteinds, linkinds)) - setindex_preserve_graph!(tn, tensor_v, v) - end - return tn + itensor_constructor::Function, is::IndsNetwork; link_space = trivial_space(is), kwargs... + ) + is = insert_linkinds(is; link_space) + tn = ITensorNetwork{vertextype(is)}() + for v in vertices(is) + add_vertex!(tn, v) + end + for e in edges(is) + add_edge!(tn, e) + end + for v in vertices(tn) + # TODO: Replace with `is[v]` once `getindex(::IndsNetwork, ...)` is smarter. + siteinds = get(is, v, Index[]) + edges = [edgetype(is)(v, nv) for nv in neighbors(is, v)] + linkinds = map(e -> is[e], Indices(edges)) + tensor_v = generic_state(itensor_constructor(v), (; siteinds, linkinds)) + setindex_preserve_graph!(tn, tensor_v, v) + end + return tn end ITensorNetwork(itns::Vector{ITensorNetwork}) = reduce(⊗, itns) # TODO: Use `vertex_data` here? function eachtensor(ψ::ITensorNetwork) - # This type declaration is needed to narrow - # the element type of the resulting `Dictionary`, - # raise and issue with `Dictionaries.jl`. - return map(v -> ψ[v]::ITensor, vertices(ψ)) + # This type declaration is needed to narrow + # the element type of the resulting `Dictionary`, + # raise and issue with `Dictionaries.jl`. + return map(v -> ψ[v]::ITensor, vertices(ψ)) end diff --git a/src/lib/ITensorsExtensions/src/itensor.jl b/src/lib/ITensorsExtensions/src/itensor.jl index cc03865f..d37be93d 100644 --- a/src/lib/ITensorsExtensions/src/itensor.jl +++ b/src/lib/ITensorsExtensions/src/itensor.jl @@ -1,47 +1,47 @@ using LinearAlgebra: LinearAlgebra, eigen, pinv using ITensors: - ITensors, - ITensor, - Index, - commonind, - dag, - dir, - hasqns, - indices, - inds, - isdiag, - itensor, - map_diag, - noncommonind, - noprime, - permute, - replaceind, - replaceinds, - sim, - space, - sqrt_decomp + ITensors, + ITensor, + Index, + commonind, + dag, + dir, + hasqns, + indices, + inds, + isdiag, + itensor, + map_diag, + noncommonind, + noprime, + permute, + replaceind, + replaceinds, + sim, + space, + sqrt_decomp using ITensors.NDTensors: - NDTensors, - Block, - Tensor, - blockdim, - blockoffsets, - denseblocks, - diaglength, - getdiagindex, - nzblocks, - setdiagindex!, - svd, - tensor, - DiagBlockSparseTensor, - DenseTensor, - BlockOffsets + NDTensors, + Block, + Tensor, + blockdim, + blockoffsets, + denseblocks, + diaglength, + getdiagindex, + nzblocks, + setdiagindex!, + svd, + tensor, + DiagBlockSparseTensor, + DenseTensor, + BlockOffsets function NDTensors.blockoffsets(dense::DenseTensor) - return BlockOffsets{ndims(dense)}([Block(ntuple(Returns(1), ndims(dense)))], [0]) + return BlockOffsets{ndims(dense)}([Block(ntuple(Returns(1), ndims(dense)))], [0]) end function NDTensors.nzblocks(dense::DenseTensor) - return nzblocks(blockoffsets(dense)) + return nzblocks(blockoffsets(dense)) end NDTensors.blockdim(ind::Int, ::Block{1}) = ind NDTensors.blockdim(i::Index{Int}, b::Integer) = blockdim(i, Block(b)) @@ -57,75 +57,75 @@ pinv_diag(it::ITensor) = map_diag(pinv, it) pinvsqrt_diag(it::ITensor) = map_diag(pinv ∘ sqrt, it) #TODO: Make this work for non-hermitian A -function eigendecomp(A::ITensor, linds, rinds; ishermitian=false, kwargs...) - @assert ishermitian - D, U = eigen(A, linds, rinds; ishermitian, kwargs...) - ul, ur = noncommonind(D, U), commonind(D, U) - Ul = replaceinds(U, vcat(rinds, ur), vcat(linds, ul)) - return Ul, D, dag(U) +function eigendecomp(A::ITensor, linds, rinds; ishermitian = false, kwargs...) + @assert ishermitian + D, U = eigen(A, linds, rinds; ishermitian, kwargs...) + ul, ur = noncommonind(D, U), commonind(D, U) + Ul = replaceinds(U, vcat(rinds, ur), vcat(linds, ul)) + return Ul, D, dag(U) end function make_bosonic(A::ITensor, Linds, Rinds) - # Bring indices into i',j',..,dag(j),dag(i) - # ordering with Out indices coming before In indices - # Resulting tensor acts like a normal matrix (no extra signs - # when taking powers A^n) - Linds = indices(Linds) - Rinds = indices(Rinds) - if all(j->dir(j)==ITensors.Out, Linds) && all(j->dir(j)==ITensors.In, Rinds) - ordered_inds = [Linds..., reverse(Rinds)...] - elseif all(j->dir(j)==ITensors.Out, Rinds) && all(j->dir(j)==ITensors.In, Linds) - ordered_inds = [Rinds..., reverse(Linds)...] - else - error( - "For fermionic exp, Linds and Rinds must have same directions within each set. Got dir.(Linds)=", - dir.(Linds), - ", dir.(Rinds)=", - dir.(Rinds), - ) - end - # permuted A^n will be sign free, ok to temporarily disable fermion system - return permute(A, ordered_inds) + # Bring indices into i',j',..,dag(j),dag(i) + # ordering with Out indices coming before In indices + # Resulting tensor acts like a normal matrix (no extra signs + # when taking powers A^n) + Linds = indices(Linds) + Rinds = indices(Rinds) + if all(j -> dir(j) == ITensors.Out, Linds) && all(j -> dir(j) == ITensors.In, Rinds) + ordered_inds = [Linds..., reverse(Rinds)...] + elseif all(j -> dir(j) == ITensors.Out, Rinds) && all(j -> dir(j) == ITensors.In, Linds) + ordered_inds = [Rinds..., reverse(Linds)...] + else + error( + "For fermionic exp, Linds and Rinds must have same directions within each set. Got dir.(Linds)=", + dir.(Linds), + ", dir.(Rinds)=", + dir.(Rinds), + ) + end + # permuted A^n will be sign free, ok to temporarily disable fermion system + return permute(A, ordered_inds) end function map_eigvals(f::Function, A::ITensor, Linds, Rinds; kws...) - # - fermionic_itensor = - ITensors.using_auto_fermion() && ITensors.has_fermionic_subspaces(inds(A)) - if fermionic_itensor - A = make_bosonic(A::ITensor, Linds, Rinds) - ordered_inds = inds(A) - ITensors.disable_auto_fermion() - end + # + fermionic_itensor = + ITensors.using_auto_fermion() && ITensors.has_fermionic_subspaces(inds(A)) + if fermionic_itensor + A = make_bosonic(A::ITensor, Linds, Rinds) + ordered_inds = inds(A) + ITensors.disable_auto_fermion() + end - if isdiag(A) - mapped_A = map_diag(f, A) - else - Ul, D, Ur = eigendecomp(A, Linds, Rinds; kws...) - mapped_A = Ul * map_diag(f, D) * Ur - end + if isdiag(A) + mapped_A = map_diag(f, A) + else + Ul, D, Ur = eigendecomp(A, Linds, Rinds; kws...) + mapped_A = Ul * map_diag(f, D) * Ur + end - # - if fermionic_itensor - # Ensure indices in "matrix" form before re-enabling fermion system - mapped_A = permute(mapped_A, ordered_inds) - ITensors.enable_auto_fermion() - end + # + if fermionic_itensor + # Ensure indices in "matrix" form before re-enabling fermion system + mapped_A = permute(mapped_A, ordered_inds) + ITensors.enable_auto_fermion() + end - return mapped_A + return mapped_A end # Analagous to `denseblocks`. # Extract the diagonal entries into a diagonal tensor. function diagblocks(D::Tensor) - nzblocksD = nzblocks(D) - T = DiagBlockSparseTensor(eltype(D), nzblocksD, inds(D)) - for b in nzblocksD - for n in 1:diaglength(D) - setdiagindex!(T, getdiagindex(D, n), n) + nzblocksD = nzblocks(D) + T = DiagBlockSparseTensor(eltype(D), nzblocksD, inds(D)) + for b in nzblocksD + for n in 1:diaglength(D) + setdiagindex!(T, getdiagindex(D, n), n) + end end - end - return T + return T end diagblocks(it::ITensor) = itensor(diagblocks(tensor(it))) diff --git a/src/lib/ITensorsExtensions/src/itensor_more.jl b/src/lib/ITensorsExtensions/src/itensor_more.jl index 1aa5bb5a..a4abd10d 100644 --- a/src/lib/ITensorsExtensions/src/itensor_more.jl +++ b/src/lib/ITensorsExtensions/src/itensor_more.jl @@ -9,15 +9,15 @@ using Dictionaries: AbstractDictionary, Dictionary # C. S. KUBRUSLY and N. LEVAN # https://www.emis.de/journals/AMUC/_vol-80/_no_1/_kubrusly/kubrusly.pdf function tensor_sum(A::ITensor, B::ITensor) - extend_A = filterinds(uniqueinds(B, A); plev=0) - extend_B = filterinds(uniqueinds(A, B); plev=0) - for i in extend_A - A *= op("I", i) - end - for i in extend_B - B *= op("I", i) - end - return A + B + extend_A = filterinds(uniqueinds(B, A); plev = 0) + extend_B = filterinds(uniqueinds(A, B); plev = 0) + for i in extend_A + A *= op("I", i) + end + for i in extend_B + B *= op("I", i) + end + return A + B end # Patch for contraction sequences with `Key` @@ -26,7 +26,7 @@ end ITensors._contract(As, index::Key) = As[index] # TODO: Replace with a trait of the same name. -const IsIndexSpace = Union{<:Integer,Vector{<:Pair{QN,<:Integer}}} +const IsIndexSpace = Union{<:Integer, Vector{<:Pair{QN, <:Integer}}} # Infer the `Index` type of an `IndsNetwork` from the # spaces that get input. @@ -54,37 +54,37 @@ spacetype(::Index{T}) where {T} = T spacetype(::Type{<:Index{T}}) where {T} = T function promote_indtype(is::Vararg{Type{<:Index}}) - return reduce(promote_indtype_rule, is; init=Index{Int}) + return reduce(promote_indtype_rule, is; init = Index{Int}) end function promote_spacetype_rule(type1::Type, type2::Type) - return error("Not implemented") + return error("Not implemented") end function promote_spacetype_rule( - type1::Type{<:Integer}, type2::Type{<:Vector{<:Pair{QN,T2}}} -) where {T2<:Integer} - return Vector{Pair{QN,promote_type(type1, T2)}} + type1::Type{<:Integer}, type2::Type{<:Vector{<:Pair{QN, T2}}} + ) where {T2 <: Integer} + return Vector{Pair{QN, promote_type(type1, T2)}} end function promote_spacetype_rule( - type1::Type{<:Vector{<:Pair{QN,<:Integer}}}, type2::Type{<:Integer} -) - return promote_spacetype_rule(type2, type1) + type1::Type{<:Vector{<:Pair{QN, <:Integer}}}, type2::Type{<:Integer} + ) + return promote_spacetype_rule(type2, type1) end function promote_spacetype_rule( - type1::Type{<:Vector{<:Pair{QN,T1}}}, type2::Type{<:Vector{<:Pair{QN,T2}}} -) where {T1<:Integer,T2<:Integer} - return Vector{Pair{QN,promote_type(T1, T2)}} + type1::Type{<:Vector{<:Pair{QN, T1}}}, type2::Type{<:Vector{<:Pair{QN, T2}}} + ) where {T1 <: Integer, T2 <: Integer} + return Vector{Pair{QN, promote_type(T1, T2)}} end function promote_spacetype_rule(type1::Type{<:Integer}, type2::Type{<:Integer}) - return promote_type(type1, type2) + return promote_type(type1, type2) end function promote_indtype_rule(type1::Type{<:Index}, type2::Type{<:Index}) - return Index{promote_spacetype_rule(spacetype(type1), spacetype(type2))} + return Index{promote_spacetype_rule(spacetype(type1), spacetype(type2))} end function promote_indtypeof end @@ -93,7 +93,7 @@ trivial_space(x) = trivial_space(promote_indtypeof(x)) trivial_space(x::Type) = trivial_space(promote_indtype(x)) trivial_space(i::Type{<:Index{<:Integer}}) = 1 -trivial_space(i::Type{<:Index{<:Vector{<:Pair{<:QN,<:Integer}}}}) = [QN() => 1] +trivial_space(i::Type{<:Index{<:Vector{<:Pair{<:QN, <:Integer}}}}) = [QN() => 1] """ Given an input tensor and a Dict (ind_to_newind), replace inds of tensor that are also @@ -103,17 +103,17 @@ ITensors.replaceinds(tensor, collect(keys(ind_to_newind)) => collect(values(ind_ Based on benchmark, this implementation is more efficient when the size of ind_to_newind is large. TODO: we can remove this function once the original replaceinds performance is improved. """ -function ITensors.replaceinds(tensor::ITensor, ind_to_newind::Dict{<:Index,<:Index}) - subset_inds = intersect(inds(tensor), collect(keys(ind_to_newind))) - if length(subset_inds) == 0 - return tensor - end - out_inds = map(i -> ind_to_newind[i], subset_inds) - return replaceinds(tensor, subset_inds => out_inds) +function ITensors.replaceinds(tensor::ITensor, ind_to_newind::Dict{<:Index, <:Index}) + subset_inds = intersect(inds(tensor), collect(keys(ind_to_newind))) + if length(subset_inds) == 0 + return tensor + end + out_inds = map(i -> ind_to_newind[i], subset_inds) + return replaceinds(tensor, subset_inds => out_inds) end is_delta(it::ITensor) = is_delta(NDTensors.tensor(it)) is_delta(t::NDTensors.Tensor) = false function is_delta(t::NDTensors.UniformDiagTensor) - return isone(NDTensors.getdiagindex(t, 1)) + return isone(NDTensors.getdiagindex(t, 1)) end diff --git a/src/lib/ITensorsExtensions/src/opsum.jl b/src/lib/ITensorsExtensions/src/opsum.jl index 783ed4fb..60c2adef 100644 --- a/src/lib/ITensorsExtensions/src/opsum.jl +++ b/src/lib/ITensorsExtensions/src/opsum.jl @@ -8,21 +8,21 @@ using SplitApplyCombine: group # TODO: Rename this `replace_sites`? # TODO: Use `fmap`, `deepmap`, `treemap`? function replace_vertices(f, ∑o::Sum) - return Sum(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∑o))) + return Sum(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∑o))) end function replace_vertices(f, ∏o::Prod) - return Prod(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∏o))) + return Prod(map(oᵢ -> replace_vertices(f, oᵢ), Ops.terms(∏o))) end function replace_vertices(f, o::Scaled) - return maybe_real(Ops.coefficient(o)) * replace_vertices(f, Ops.argument(o)) + return maybe_real(Ops.coefficient(o)) * replace_vertices(f, Ops.argument(o)) end set_sites(o::Op, sites) = Op(Ops.which_op(o), sites...; Ops.params(o)...) function replace_vertices(f, o::Op) - return set_sites(o, f.(Ops.sites(o))) + return set_sites(o, f.(Ops.sites(o))) end ## function replace_vertices(o::Union{Op,Applied}, vertex_map) @@ -30,10 +30,10 @@ end ## end function group_terms(ℋ::Sum, g) - grouped_terms = group(ITensors.terms(ℋ)) do t - findfirst(edges(g)) do e - to_tuple.(ITensors.sites(t)) ⊆ [src(e), dst(e)] + grouped_terms = group(ITensors.terms(ℋ)) do t + findfirst(edges(g)) do e + to_tuple.(ITensors.sites(t)) ⊆ [src(e), dst(e)] + end end - end - return Sum(collect(sum.(grouped_terms))) + return Sum(collect(sum.(grouped_terms))) end diff --git a/src/lib/ModelHamiltonians/src/ModelHamiltonians.jl b/src/lib/ModelHamiltonians/src/ModelHamiltonians.jl index f54faea3..100284f8 100644 --- a/src/lib/ModelHamiltonians/src/ModelHamiltonians.jl +++ b/src/lib/ModelHamiltonians/src/ModelHamiltonians.jl @@ -7,97 +7,97 @@ to_callable(value::Type) = value to_callable(value::Function) = value to_callable(value::AbstractDict) = Base.Fix1(getindex, value) to_callable(value::AbstractDictionary) = Base.Fix1(getindex, value) -function to_callable(value::AbstractArray{<:Any,N}) where {N} - getindex_value(x::Integer) = value[x] - getindex_value(x::Tuple{Vararg{Integer,N}}) = value[x...] - getindex_value(x::CartesianIndex{N}) = value[x] - return getindex_value +function to_callable(value::AbstractArray{<:Any, N}) where {N} + getindex_value(x::Integer) = value[x] + getindex_value(x::Tuple{Vararg{Integer, N}}) = value[x...] + getindex_value(x::CartesianIndex{N}) = value[x] + return getindex_value end to_callable(value) = Returns(value) # TODO: Move to `NamedGraphs.jl` or `GraphsExtensions.jl`. # TODO: Add a tet for this. function nth_nearest_neighbors(g, v, n::Int) - isone(n) && return neighborhood(g, v, 1) - return setdiff(neighborhood(g, v, n), neighborhood(g, v, n - 1)) + isone(n) && return neighborhood(g, v, 1) + return setdiff(neighborhood(g, v, n), neighborhood(g, v, n - 1)) end # TODO: Move to `NamedGraphs.jl` or `GraphsExtensions.jl`. # TODO: Add a tet for this. next_nearest_neighbors(g, v) = nth_nearest_neighbors(g, v, 2) -function tight_binding(g::AbstractGraph; t=1, tp=0, h=0) - (; t, tp, h) = map(to_callable, (; t, tp, h)) - h = to_callable(h) - ℋ = OpSum() - for e in edges(g) - ℋ -= t(e), "Cdag", src(e), "C", dst(e) - ℋ -= t(e), "Cdag", dst(e), "C", src(e) - end - for v in vertices(g) - for nn in next_nearest_neighbors(g, v) - e = edgetype(g)(v, nn) - ℋ -= tp(e), "Cdag", src(e), "C", dst(e) - ℋ -= tp(e), "Cdag", dst(e), "C", src(e) +function tight_binding(g::AbstractGraph; t = 1, tp = 0, h = 0) + (; t, tp, h) = map(to_callable, (; t, tp, h)) + h = to_callable(h) + ℋ = OpSum() + for e in edges(g) + ℋ -= t(e), "Cdag", src(e), "C", dst(e) + ℋ -= t(e), "Cdag", dst(e), "C", src(e) end - end - for v in vertices(g) - ℋ -= h(v), "N", v - end - return ℋ + for v in vertices(g) + for nn in next_nearest_neighbors(g, v) + e = edgetype(g)(v, nn) + ℋ -= tp(e), "Cdag", src(e), "C", dst(e) + ℋ -= tp(e), "Cdag", dst(e), "C", src(e) + end + end + for v in vertices(g) + ℋ -= h(v), "N", v + end + return ℋ end """ t-t' Hubbard Model g,i,v """ -function hubbard(g::AbstractGraph; U=0, t=1, tp=0, h=0) - (; U, t, tp, h) = map(to_callable, (; U, t, tp, h)) - ℋ = OpSum() - for e in edges(g) - ℋ -= t(e), "Cdagup", src(e), "Cup", dst(e) - ℋ -= t(e), "Cdagup", dst(e), "Cup", src(e) - ℋ -= t(e), "Cdagdn", src(e), "Cdn", dst(e) - ℋ -= t(e), "Cdagdn", dst(e), "Cdn", src(e) - end - for v in vertices(g) - for nn in next_nearest_neighbors(g, v) - e = edgetype(g)(v, nn) - ℋ -= tp(e), "Cdagup", src(e), "Cup", dst(e) - ℋ -= tp(e), "Cdagup", dst(e), "Cup", src(e) - ℋ -= tp(e), "Cdagdn", src(e), "Cdn", dst(e) - ℋ -= tp(e), "Cdagdn", dst(e), "Cdn", src(e) +function hubbard(g::AbstractGraph; U = 0, t = 1, tp = 0, h = 0) + (; U, t, tp, h) = map(to_callable, (; U, t, tp, h)) + ℋ = OpSum() + for e in edges(g) + ℋ -= t(e), "Cdagup", src(e), "Cup", dst(e) + ℋ -= t(e), "Cdagup", dst(e), "Cup", src(e) + ℋ -= t(e), "Cdagdn", src(e), "Cdn", dst(e) + ℋ -= t(e), "Cdagdn", dst(e), "Cdn", src(e) + end + for v in vertices(g) + for nn in next_nearest_neighbors(g, v) + e = edgetype(g)(v, nn) + ℋ -= tp(e), "Cdagup", src(e), "Cup", dst(e) + ℋ -= tp(e), "Cdagup", dst(e), "Cup", src(e) + ℋ -= tp(e), "Cdagdn", src(e), "Cdn", dst(e) + ℋ -= tp(e), "Cdagdn", dst(e), "Cdn", src(e) + end + end + for v in vertices(g) + ℋ -= h(v), "Sz", v + ℋ += U(v), "Nupdn", v end - end - for v in vertices(g) - ℋ -= h(v), "Sz", v - ℋ += U(v), "Nupdn", v - end - return ℋ + return ℋ end """ Random field J1-J2 Heisenberg model on a general graph """ -function heisenberg(g::AbstractGraph; J1=1, J2=0, h=0) - (; J1, J2, h) = map(to_callable, (; J1, J2, h)) - ℋ = OpSum() - for e in edges(g) - ℋ += J1(e) / 2, "S+", src(e), "S-", dst(e) - ℋ += J1(e) / 2, "S-", src(e), "S+", dst(e) - ℋ += J1(e), "Sz", src(e), "Sz", dst(e) - end - for v in vertices(g) - for nn in next_nearest_neighbors(g, v) - e = edgetype(g)(v, nn) - ℋ += J2(e) / 2, "S+", src(e), "S-", dst(e) - ℋ += J2(e) / 2, "S-", src(e), "S+", dst(e) - ℋ += J2(e), "Sz", src(e), "Sz", dst(e) +function heisenberg(g::AbstractGraph; J1 = 1, J2 = 0, h = 0) + (; J1, J2, h) = map(to_callable, (; J1, J2, h)) + ℋ = OpSum() + for e in edges(g) + ℋ += J1(e) / 2, "S+", src(e), "S-", dst(e) + ℋ += J1(e) / 2, "S-", src(e), "S+", dst(e) + ℋ += J1(e), "Sz", src(e), "Sz", dst(e) end - end - for v in vertices(g) - ℋ += h(v), "Sz", v - end - return ℋ + for v in vertices(g) + for nn in next_nearest_neighbors(g, v) + e = edgetype(g)(v, nn) + ℋ += J2(e) / 2, "S+", src(e), "S-", dst(e) + ℋ += J2(e) / 2, "S-", src(e), "S+", dst(e) + ℋ += J2(e), "Sz", src(e), "Sz", dst(e) + end + end + for v in vertices(g) + ℋ += h(v), "Sz", v + end + return ℋ end """ @@ -108,29 +108,29 @@ heisenberg(N::Integer; kwargs...) = heisenberg(path_graph(N); kwargs...) """ Next-to-nearest-neighbor Ising model (ZZX) on a general graph """ -function ising(g::AbstractGraph; J1=-1, J2=0, h=0) - (; J1, J2, h) = map(to_callable, (; J1, J2, h)) - ℋ = OpSum() - for e in edges(g) - ℋ += J1(e), "Sz", src(e), "Sz", dst(e) - end - for v in vertices(g) - for nn in next_nearest_neighbors(g, v) - e = edgetype(g)(v, nn) - # TODO: Try removing this if-statement. This - # helps to avoid constructing next-nearest - # neighbor gates, which `apply` can't handle - # right now. We could skip zero terms in gate - # construction. - if !iszero(J2(e)) - ℋ += J2(e), "Sz", src(e), "Sz", dst(e) - end +function ising(g::AbstractGraph; J1 = -1, J2 = 0, h = 0) + (; J1, J2, h) = map(to_callable, (; J1, J2, h)) + ℋ = OpSum() + for e in edges(g) + ℋ += J1(e), "Sz", src(e), "Sz", dst(e) + end + for v in vertices(g) + for nn in next_nearest_neighbors(g, v) + e = edgetype(g)(v, nn) + # TODO: Try removing this if-statement. This + # helps to avoid constructing next-nearest + # neighbor gates, which `apply` can't handle + # right now. We could skip zero terms in gate + # construction. + if !iszero(J2(e)) + ℋ += J2(e), "Sz", src(e), "Sz", dst(e) + end + end + end + for v in vertices(g) + ℋ += h(v), "Sx", v end - end - for v in vertices(g) - ℋ += h(v), "Sx", v - end - return ℋ + return ℋ end """ diff --git a/src/lib/ModelNetworks/src/ModelNetworks.jl b/src/lib/ModelNetworks/src/ModelNetworks.jl index 88fee784..8bf7292d 100644 --- a/src/lib/ModelNetworks/src/ModelNetworks.jl +++ b/src/lib/ModelNetworks/src/ModelNetworks.jl @@ -15,68 +15,68 @@ OPTIONAL ARGUMENT: INDSNETWORK IS ASSUMED TO BE BUILT FROM A GRAPH (NO SITE INDS) AND OF LINK SPACE 2 """ function ising_network( - eltype::Type, s::IndsNetwork, beta::Number; h::Number=0.0, szverts=nothing -) - s = insert_linkinds(s; link_space=2) - tn = delta_network(eltype, s) - if (szverts != nothing) - for v in szverts - tn[v] = diag_itensor(eltype[1, -1], inds(tn[v])) + eltype::Type, s::IndsNetwork, beta::Number; h::Number = 0.0, szverts = nothing + ) + s = insert_linkinds(s; link_space = 2) + tn = delta_network(eltype, s) + if (szverts != nothing) + for v in szverts + tn[v] = diag_itensor(eltype[1, -1], inds(tn[v])) + end end - end - for edge in edges(tn) - v1 = src(edge) - v2 = dst(edge) - i = commoninds(tn[v1], tn[v2])[1] - deg_v1 = degree(tn, v1) - deg_v2 = degree(tn, v2) - f11 = exp(beta * (1 + h / deg_v1 + h / deg_v2)) - f12 = exp(beta * (-1 + h / deg_v1 - h / deg_v2)) - f21 = exp(beta * (-1 - h / deg_v1 + h / deg_v2)) - f22 = exp(beta * (1 - h / deg_v1 - h / deg_v2)) - q = eltype[f11 f12; f21 f22] - w, V = eigen(q) - w = map(sqrt, w) - sqrt_q = V * Diagonal(w) * inv(V) - t = itensor(sqrt_q, i, i') - tn[v1] = tn[v1] * t - tn[v1] = noprime(tn[v1]) - t = itensor(sqrt_q, i', i) - tn[v2] = tn[v2] * t - tn[v2] = noprime(tn[v2]) - end - return tn + for edge in edges(tn) + v1 = src(edge) + v2 = dst(edge) + i = commoninds(tn[v1], tn[v2])[1] + deg_v1 = degree(tn, v1) + deg_v2 = degree(tn, v2) + f11 = exp(beta * (1 + h / deg_v1 + h / deg_v2)) + f12 = exp(beta * (-1 + h / deg_v1 - h / deg_v2)) + f21 = exp(beta * (-1 - h / deg_v1 + h / deg_v2)) + f22 = exp(beta * (1 - h / deg_v1 - h / deg_v2)) + q = eltype[f11 f12; f21 f22] + w, V = eigen(q) + w = map(sqrt, w) + sqrt_q = V * Diagonal(w) * inv(V) + t = itensor(sqrt_q, i, i') + tn[v1] = tn[v1] * t + tn[v1] = noprime(tn[v1]) + t = itensor(sqrt_q, i', i) + tn[v2] = tn[v2] * t + tn[v2] = noprime(tn[v2]) + end + return tn end -function ising_network(s::IndsNetwork, beta::Number; h::Number=0.0, szverts=nothing) - return ising_network(typeof(beta), s, beta; h, szverts) +function ising_network(s::IndsNetwork, beta::Number; h::Number = 0.0, szverts = nothing) + return ising_network(typeof(beta), s, beta; h, szverts) end function ising_network( - eltype::Type, g::NamedGraph, beta::Number; h::Number=0.0, szverts=nothing -) - return ising_network(eltype, IndsNetwork(g; link_space=2), beta; h, szverts) + eltype::Type, g::NamedGraph, beta::Number; h::Number = 0.0, szverts = nothing + ) + return ising_network(eltype, IndsNetwork(g; link_space = 2), beta; h, szverts) end -function ising_network(g::NamedGraph, beta::Number; h::Number=0.0, szverts=nothing) - return ising_network(eltype(beta), g, beta; h, szverts) +function ising_network(g::NamedGraph, beta::Number; h::Number = 0.0, szverts = nothing) + return ising_network(eltype(beta), g, beta; h, szverts) end """Build the wavefunction whose norm is equal to Z of the classical ising model s needs to have site indices in this case!""" -function ising_network_state(eltype::Type, s::IndsNetwork, beta::Number; h::Number=0.0) - return ising_network(eltype, s, 0.5 * beta; h) +function ising_network_state(eltype::Type, s::IndsNetwork, beta::Number; h::Number = 0.0) + return ising_network(eltype, s, 0.5 * beta; h) end -function ising_network_state(eltype::Type, g::NamedGraph, beta::Number; h::Number=0.0) - return ising_network(eltype, IndsNetwork(g, 2, 2), 0.5 * beta; h) +function ising_network_state(eltype::Type, g::NamedGraph, beta::Number; h::Number = 0.0) + return ising_network(eltype, IndsNetwork(g, 2, 2), 0.5 * beta; h) end -function ising_network_state(s::IndsNetwork, beta::Number; h::Number=0.0) - return ising_network_state(typeof(beta), s, beta; h) +function ising_network_state(s::IndsNetwork, beta::Number; h::Number = 0.0) + return ising_network_state(typeof(beta), s, beta; h) end -function ising_network_state(g::NamedGraph, beta::Number; h::Number=0.0) - return ising_network(typeof(beta), IndsNetwork(g, 2, 2), 0.5 * beta; h) +function ising_network_state(g::NamedGraph, beta::Number; h::Number = 0.0) + return ising_network(typeof(beta), IndsNetwork(g, 2, 2), 0.5 * beta; h) end end diff --git a/src/normalize.jl b/src/normalize.jl index c9dc4a9a..ba564ac0 100644 --- a/src/normalize.jl +++ b/src/normalize.jl @@ -1,72 +1,72 @@ using LinearAlgebra: normalize -function rescale(tn::AbstractITensorNetwork; alg="exact", kwargs...) - return rescale(Algorithm(alg), tn; kwargs...) +function rescale(tn::AbstractITensorNetwork; alg = "exact", kwargs...) + return rescale(Algorithm(alg), tn; kwargs...) end function rescale(alg::Algorithm"exact", tn::AbstractITensorNetwork; kwargs...) - logn = logscalar(alg, tn; kwargs...) - vs = collect(vertices(tn)) - c = inv(exp(logn / length(vs))) - vertices_weights = Dictionary(vs, [c for v in vs]) - return scale(tn, vertices_weights) + logn = logscalar(alg, tn; kwargs...) + vs = collect(vertices(tn)) + c = inv(exp(logn / length(vs))) + vertices_weights = Dictionary(vs, [c for v in vs]) + return scale(tn, vertices_weights) end function rescale( - alg::Algorithm, - tn::AbstractITensorNetwork, - args...; - (cache!)=nothing, - cache_construction_kwargs=default_cache_construction_kwargs(alg, tn), - update_cache=isnothing(cache!), - cache_update_kwargs=(;), - kwargs..., -) - if isnothing(cache!) - cache! = Ref(cache(alg, tn; cache_construction_kwargs...)) - end + alg::Algorithm, + tn::AbstractITensorNetwork, + args...; + (cache!) = nothing, + cache_construction_kwargs = default_cache_construction_kwargs(alg, tn), + update_cache = isnothing(cache!), + cache_update_kwargs = (;), + kwargs..., + ) + if isnothing(cache!) + cache! = Ref(cache(alg, tn; cache_construction_kwargs...)) + end - if update_cache - cache![] = update(cache![]; cache_update_kwargs...) - end + if update_cache + cache![] = update(cache![]; cache_update_kwargs...) + end - cache![] = rescale(cache![], args...; kwargs...) + cache![] = rescale(cache![], args...; kwargs...) - return tensornetwork(cache![]) + return tensornetwork(cache![]) end -function LinearAlgebra.normalize(tn::AbstractITensorNetwork; alg="exact", kwargs...) - return normalize(Algorithm(alg), tn; kwargs...) +function LinearAlgebra.normalize(tn::AbstractITensorNetwork; alg = "exact", kwargs...) + return normalize(Algorithm(alg), tn; kwargs...) end function LinearAlgebra.normalize( - alg::Algorithm"exact", tn::AbstractITensorNetwork; kwargs... -) - logn = logscalar(alg, inner_network(tn, tn); kwargs...) - vs = collect(vertices(tn)) - c = inv(exp(logn / (2*length(vs)))) - vertices_weights = Dictionary(vs, [c for v in vs]) - return scale(tn, vertices_weights) + alg::Algorithm"exact", tn::AbstractITensorNetwork; kwargs... + ) + logn = logscalar(alg, inner_network(tn, tn); kwargs...) + vs = collect(vertices(tn)) + c = inv(exp(logn / (2 * length(vs)))) + vertices_weights = Dictionary(vs, [c for v in vs]) + return scale(tn, vertices_weights) end function LinearAlgebra.normalize( - alg::Algorithm, - tn::AbstractITensorNetwork; - (cache!)=nothing, - cache_construction_function=tn -> - cache(alg, tn; default_cache_construction_kwargs(alg, tn)...), - update_cache=isnothing(cache!), - cache_update_kwargs=(;), - cache_construction_kwargs=(;), -) - norm_tn = inner_network(tn, tn) - if isnothing(cache!) - cache! = Ref(cache(alg, norm_tn; cache_construction_kwargs...)) - end + alg::Algorithm, + tn::AbstractITensorNetwork; + (cache!) = nothing, + cache_construction_function = tn -> + cache(alg, tn; default_cache_construction_kwargs(alg, tn)...), + update_cache = isnothing(cache!), + cache_update_kwargs = (;), + cache_construction_kwargs = (;), + ) + norm_tn = inner_network(tn, tn) + if isnothing(cache!) + cache! = Ref(cache(alg, norm_tn; cache_construction_kwargs...)) + end - vs = collect(vertices(tn)) - verts = vcat([ket_vertex(norm_tn, v) for v in vs], [bra_vertex(norm_tn, v) for v in vs]) - norm_tn = rescale(alg, norm_tn; verts, cache!, update_cache, cache_update_kwargs) + vs = collect(vertices(tn)) + verts = vcat([ket_vertex(norm_tn, v) for v in vs], [bra_vertex(norm_tn, v) for v in vs]) + norm_tn = rescale(alg, norm_tn; verts, cache!, update_cache, cache_update_kwargs) - return ket_network(norm_tn) + return ket_network(norm_tn) end diff --git a/src/opsum.jl b/src/opsum.jl index 8ea5cd13..fe7e6ecf 100644 --- a/src/opsum.jl +++ b/src/opsum.jl @@ -5,44 +5,44 @@ using ITensors.Ops: Ops, Op using .ITensorsExtensions: tensor_sum function ITensors.ITensor(o::Op, s::IndsNetwork) - s⃗ = [only(s[nᵢ]) for nᵢ in Ops.sites(o)] - return op(Ops.which_op(o), s⃗; Ops.params(o)...) + s⃗ = [only(s[nᵢ]) for nᵢ in Ops.sites(o)] + return op(Ops.which_op(o), s⃗; Ops.params(o)...) end function ITensors.ITensor(∏o::Prod, s::IndsNetwork) - T = ITensor(1.0) - for oᵢ in Ops.terms(∏o) - Tᵢ = ITensor(oᵢ, s) - # For now, only support operators on distinct - # sites. - @assert !hascommoninds(T, Tᵢ) - T *= Tᵢ - end - return T + T = ITensor(1.0) + for oᵢ in Ops.terms(∏o) + Tᵢ = ITensor(oᵢ, s) + # For now, only support operators on distinct + # sites. + @assert !hascommoninds(T, Tᵢ) + T *= Tᵢ + end + return T end function ITensors.ITensor(∑o::Sum, s::IndsNetwork) - T = ITensor(0) - for oᵢ in Ops.terms(∑o) - Tᵢ = ITensor(oᵢ, s) - T = tensor_sum(T, Tᵢ) - end - return T + T = ITensor(0) + for oᵢ in Ops.terms(∑o) + Tᵢ = ITensor(oᵢ, s) + T = tensor_sum(T, Tᵢ) + end + return T end function ITensors.ITensor(o::Scaled, s::IndsNetwork) - return maybe_real(Ops.coefficient(o)) * ITensor(Ops.argument(o), s) + return maybe_real(Ops.coefficient(o)) * ITensor(Ops.argument(o), s) end function ITensors.ITensor(o::Ops.Exp, s::IndsNetwork) - return exp(ITensor(Ops.argument(o), s)) + return exp(ITensor(Ops.argument(o), s)) end -function Base.Vector{ITensor}(o::Union{Sum,Prod}, s::IndsNetwork) - T⃗ = ITensor[] - for oᵢ in Ops.terms(o) - Tᵢ = ITensor(oᵢ, s) - T⃗ = [T⃗; Tᵢ] - end - return T⃗ +function Base.Vector{ITensor}(o::Union{Sum, Prod}, s::IndsNetwork) + T⃗ = ITensor[] + for oᵢ in Ops.terms(o) + Tᵢ = ITensor(oᵢ, s) + T⃗ = [T⃗; Tᵢ] + end + return T⃗ end diff --git a/src/partitioneditensornetwork.jl b/src/partitioneditensornetwork.jl index eab99de1..e1bf7700 100644 --- a/src/partitioneditensornetwork.jl +++ b/src/partitioneditensornetwork.jl @@ -4,7 +4,7 @@ using NamedGraphs.GraphsExtensions: subgraph using NamedGraphs.PartitionedGraphs: PartitionedGraph, PartitionEdge function linkinds(pitn::PartitionedGraph, edge::PartitionEdge) - src_e_itn = subgraph(pitn, src(edge)) - dst_e_itn = subgraph(pitn, dst(edge)) - return commoninds(src_e_itn, dst_e_itn) + src_e_itn = subgraph(pitn, src(edge)) + dst_e_itn = subgraph(pitn, dst(edge)) + return commoninds(src_e_itn, dst_e_itn) end diff --git a/src/sitetype.jl b/src/sitetype.jl index 0b893a82..e06fc7cf 100644 --- a/src/sitetype.jl +++ b/src/sitetype.jl @@ -3,38 +3,38 @@ using Graphs: AbstractGraph, nv, vertices using ITensors: ITensors, Index, siteind function ITensors.siteind(sitetype::String, v::Tuple; kwargs...) - return addtags(siteind(sitetype; kwargs...), vertex_tag(v)) + return addtags(siteind(sitetype; kwargs...), vertex_tag(v)) end # naming collision of ITensors.addtags and addtags keyword in siteind system -function ITensors.siteind(d::Integer, v; addtags="", kwargs...) - return ITensors.addtags(Index(d; tags="Site, $addtags", kwargs...), vertex_tag(v)) +function ITensors.siteind(d::Integer, v; addtags = "", kwargs...) + return ITensors.addtags(Index(d; tags = "Site, $addtags", kwargs...), vertex_tag(v)) end to_siteinds_callable(x) = Returns(x) function to_siteinds_callable(x::AbstractDictionary) - return Base.Fix1(getindex, x) ∘ keytype(x) + return Base.Fix1(getindex, x) ∘ keytype(x) end function siteinds(x, g::AbstractGraph; kwargs...) - return siteinds(to_siteinds_callable(x), g; kwargs...) + return siteinds(to_siteinds_callable(x), g; kwargs...) end # Convenient syntax for path graph. function siteinds(x, nv::Int; kwargs...) - return siteinds(x, path_graph(nv); kwargs...) + return siteinds(x, path_graph(nv); kwargs...) end function to_siteind(x, vertex; kwargs...) - return [siteind(x, vertex_tag(vertex); kwargs...)] + return [siteind(x, vertex_tag(vertex); kwargs...)] end to_siteind(x::Index, vertex; kwargs...) = [x] function siteinds(f::Function, g::AbstractGraph; kwargs...) - is = IndsNetwork(g) - for v in vertices(g) - is[v] = to_siteind(f(v), v; kwargs...) - end - return is + is = IndsNetwork(g) + for v in vertices(g) + is[v] = to_siteind(f(v), v; kwargs...) + end + return is end diff --git a/src/solvers/alternating_update/alternating_update.jl b/src/solvers/alternating_update/alternating_update.jl index 69f965f0..584aee3d 100644 --- a/src/solvers/alternating_update/alternating_update.jl +++ b/src/solvers/alternating_update/alternating_update.jl @@ -2,110 +2,110 @@ using ITensors: state using NamedGraphs.GraphsExtensions: GraphsExtensions function alternating_update( - operator, - init_state::AbstractTTN; - nsweeps, # define default for each solver implementation - nsites, # define default for each level of solver implementation - updater, # this specifies the update performed locally - outputlevel=default_outputlevel(), - region_printer=default_region_printer, - sweep_printer=default_sweep_printer, - (sweep_observer!)=nothing, - (region_observer!)=nothing, - root_vertex=GraphsExtensions.default_root_vertex(init_state), - extracter_kwargs=(;), - extracter=default_extracter(), - updater_kwargs=(;), - inserter_kwargs=(;), - inserter=default_inserter(), - transform_operator_kwargs=(;), - transform_operator=default_transform_operator(), - kwargs..., -) - inserter_kwargs = (; inserter_kwargs..., kwargs...) - sweep_plans = default_sweep_plans( - nsweeps, - init_state; - root_vertex, - extracter, - extracter_kwargs, - updater, - updater_kwargs, - inserter, - inserter_kwargs, - transform_operator, - transform_operator_kwargs, - nsites, - ) - return alternating_update( - operator, - init_state, - sweep_plans; - outputlevel, - sweep_observer!, - region_observer!, - sweep_printer, - region_printer, - ) + operator, + init_state::AbstractTTN; + nsweeps, # define default for each solver implementation + nsites, # define default for each level of solver implementation + updater, # this specifies the update performed locally + outputlevel = default_outputlevel(), + region_printer = default_region_printer, + sweep_printer = default_sweep_printer, + (sweep_observer!) = nothing, + (region_observer!) = nothing, + root_vertex = GraphsExtensions.default_root_vertex(init_state), + extracter_kwargs = (;), + extracter = default_extracter(), + updater_kwargs = (;), + inserter_kwargs = (;), + inserter = default_inserter(), + transform_operator_kwargs = (;), + transform_operator = default_transform_operator(), + kwargs..., + ) + inserter_kwargs = (; inserter_kwargs..., kwargs...) + sweep_plans = default_sweep_plans( + nsweeps, + init_state; + root_vertex, + extracter, + extracter_kwargs, + updater, + updater_kwargs, + inserter, + inserter_kwargs, + transform_operator, + transform_operator_kwargs, + nsites, + ) + return alternating_update( + operator, + init_state, + sweep_plans; + outputlevel, + sweep_observer!, + region_observer!, + sweep_printer, + region_printer, + ) end function alternating_update( - projected_operator, - init_state::AbstractTTN, - sweep_plans; - outputlevel=default_outputlevel(), - checkdone=default_checkdone(), # - (sweep_observer!)=nothing, - sweep_printer=default_sweep_printer,#? - (region_observer!)=nothing, - region_printer=default_region_printer, -) - state = copy(init_state) - @assert !isnothing(sweep_plans) - for which_sweep in eachindex(sweep_plans) - sweep_plan = sweep_plans[which_sweep] - sweep_time = @elapsed begin - for which_region_update in eachindex(sweep_plan) - state, projected_operator = region_update( - projected_operator, - state; - which_sweep, - sweep_plan, - region_printer, - (region_observer!), - which_region_update, - outputlevel, + projected_operator, + init_state::AbstractTTN, + sweep_plans; + outputlevel = default_outputlevel(), + checkdone = default_checkdone(), # + (sweep_observer!) = nothing, + sweep_printer = default_sweep_printer, #? + (region_observer!) = nothing, + region_printer = default_region_printer, + ) + state = copy(init_state) + @assert !isnothing(sweep_plans) + for which_sweep in eachindex(sweep_plans) + sweep_plan = sweep_plans[which_sweep] + sweep_time = @elapsed begin + for which_region_update in eachindex(sweep_plan) + state, projected_operator = region_update( + projected_operator, + state; + which_sweep, + sweep_plan, + region_printer, + (region_observer!), + which_region_update, + outputlevel, + ) + end + end + update_observer!( + sweep_observer!; state, which_sweep, sweep_time, outputlevel, sweep_plans ) - end + !isnothing(sweep_printer) && + sweep_printer(; state, which_sweep, sweep_time, outputlevel, sweep_plans) + checkdone(; + state, + which_sweep, + outputlevel, + sweep_plan, + sweep_plans, + sweep_observer!, + region_observer!, + ) && break end - update_observer!( - sweep_observer!; state, which_sweep, sweep_time, outputlevel, sweep_plans - ) - !isnothing(sweep_printer) && - sweep_printer(; state, which_sweep, sweep_time, outputlevel, sweep_plans) - checkdone(; - state, - which_sweep, - outputlevel, - sweep_plan, - sweep_plans, - sweep_observer!, - region_observer!, - ) && break - end - return state + return state end function alternating_update(operator::AbstractTTN, init_state::AbstractTTN; kwargs...) - projected_operator = ProjTTN(operator) - return alternating_update(projected_operator, init_state; kwargs...) + projected_operator = ProjTTN(operator) + return alternating_update(projected_operator, init_state; kwargs...) end function alternating_update( - operator::AbstractTTN, init_state::AbstractTTN, sweep_plans; kwargs... -) - projected_operator = ProjTTN(operator) - return alternating_update(projected_operator, init_state, sweep_plans; kwargs...) + operator::AbstractTTN, init_state::AbstractTTN, sweep_plans; kwargs... + ) + projected_operator = ProjTTN(operator) + return alternating_update(projected_operator, init_state, sweep_plans; kwargs...) end #ToDo: Fix docstring. @@ -129,15 +129,15 @@ Returns: * `state::MPS` - time-evolved MPS """ function alternating_update( - operators::Vector{<:AbstractTTN}, init_state::AbstractTTN; kwargs... -) - projected_operators = ProjTTNSum(operators) - return alternating_update(projected_operators, init_state; kwargs...) + operators::Vector{<:AbstractTTN}, init_state::AbstractTTN; kwargs... + ) + projected_operators = ProjTTNSum(operators) + return alternating_update(projected_operators, init_state; kwargs...) end function alternating_update( - operators::Vector{<:AbstractTTN}, init_state::AbstractTTN, sweep_plans; kwargs... -) - projected_operators = ProjTTNSum(operators) - return alternating_update(projected_operators, init_state, sweep_plans; kwargs...) + operators::Vector{<:AbstractTTN}, init_state::AbstractTTN, sweep_plans; kwargs... + ) + projected_operators = ProjTTNSum(operators) + return alternating_update(projected_operators, init_state, sweep_plans; kwargs...) end diff --git a/src/solvers/alternating_update/region_update.jl b/src/solvers/alternating_update/region_update.jl index c741c82a..2b75d6ba 100644 --- a/src/solvers/alternating_update/region_update.jl +++ b/src/solvers/alternating_update/region_update.jl @@ -1,77 +1,77 @@ function region_update( - projected_operator, - state; - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - region_printer, - (region_observer!), -) - (region, region_kwargs) = sweep_plan[which_region_update] - (; - extracter, - extracter_kwargs, - updater, - updater_kwargs, - inserter, - inserter_kwargs, - transform_operator, - transform_operator_kwargs, - internal_kwargs, - ) = region_kwargs + projected_operator, + state; + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + region_printer, + (region_observer!), + ) + (region, region_kwargs) = sweep_plan[which_region_update] + (; + extracter, + extracter_kwargs, + updater, + updater_kwargs, + inserter, + inserter_kwargs, + transform_operator, + transform_operator_kwargs, + internal_kwargs, + ) = region_kwargs - # ToDo: remove orthogonality center on vertex for generality - # region carries same information - if !isnothing(transform_operator) - projected_operator = transform_operator( - state, projected_operator; outputlevel, transform_operator_kwargs... + # ToDo: remove orthogonality center on vertex for generality + # region carries same information + if !isnothing(transform_operator) + projected_operator = transform_operator( + state, projected_operator; outputlevel, transform_operator_kwargs... + ) + end + state, projected_operator, phi = extracter( + state, projected_operator, region; extracter_kwargs..., internal_kwargs + ) + # create references, in case solver does (out-of-place) modify PH or state + state! = Ref(state) + projected_operator! = Ref(projected_operator) + # args passed by reference are supposed to be modified out of place + phi, info = updater( + phi; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + updater_kwargs..., + internal_kwargs, + ) + state = state![] + projected_operator = projected_operator![] + # ToDo: implement noise term as updater + #drho = nothing + #ortho = "left" #i guess with respect to ordered vertices that's valid but may be cleaner to use next_region logic + #if noise > 0.0 && isforward(direction) + # drho = noise * noiseterm(PH, phi, ortho) # TODO: actually implement this for trees... + # so noiseterm is a solver + #end + #if isa(region, AbstractEdge) && + state, spec = inserter(state, phi, region; inserter_kwargs..., internal_kwargs) + all_kwargs = (; + which_region_update, + sweep_plan, + total_sweep_steps = length(sweep_plan), + end_of_sweep = (which_region_update == length(sweep_plan)), + state, + region, + which_sweep, + spec, + outputlevel, + info..., + region_kwargs..., + internal_kwargs..., ) - end - state, projected_operator, phi = extracter( - state, projected_operator, region; extracter_kwargs..., internal_kwargs - ) - # create references, in case solver does (out-of-place) modify PH or state - state! = Ref(state) - projected_operator! = Ref(projected_operator) - # args passed by reference are supposed to be modified out of place - phi, info = updater( - phi; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - updater_kwargs..., - internal_kwargs, - ) - state = state![] - projected_operator = projected_operator![] - # ToDo: implement noise term as updater - #drho = nothing - #ortho = "left" #i guess with respect to ordered vertices that's valid but may be cleaner to use next_region logic - #if noise > 0.0 && isforward(direction) - # drho = noise * noiseterm(PH, phi, ortho) # TODO: actually implement this for trees... - # so noiseterm is a solver - #end - #if isa(region, AbstractEdge) && - state, spec = inserter(state, phi, region; inserter_kwargs..., internal_kwargs) - all_kwargs = (; - which_region_update, - sweep_plan, - total_sweep_steps=length(sweep_plan), - end_of_sweep=(which_region_update == length(sweep_plan)), - state, - region, - which_sweep, - spec, - outputlevel, - info..., - region_kwargs..., - internal_kwargs..., - ) - update_observer!(region_observer!; all_kwargs...) - !(isnothing(region_printer)) && region_printer(; all_kwargs...) - return state, projected_operator + update_observer!(region_observer!; all_kwargs...) + !(isnothing(region_printer)) && region_printer(; all_kwargs...) + return state, projected_operator end diff --git a/src/solvers/contract.jl b/src/solvers/contract.jl index 00e5c4d6..50440fc0 100644 --- a/src/solvers/contract.jl +++ b/src/solvers/contract.jl @@ -4,83 +4,83 @@ using ITensors.NDTensors: Algorithm, @Algorithm_str, contract using NamedGraphs: vertextype function sum_contract( - ::Algorithm"fit", - tns::Vector{<:Tuple{<:AbstractTTN,<:AbstractTTN}}; - init, - nsites=2, - nsweeps=1, - cutoff=eps(), - updater=contract_updater, - kwargs..., -) - tn1s = first.(tns) - tn2s = last.(tns) - ns = nv.(tn1s) - n = first(ns) - any(ns .!= nv.(tn2s)) && throw( - DimensionMismatch("Number of sites operator ($n) and state ($(nv(tn2))) do not match") - ) - any(ns .!= n) && - throw(DimensionMismatch("Number of sites in different operators ($n) do not match")) - # ToDo: Write test for single-vertex ttn, this implementation has not been tested. - if n == 1 - res = 0 - for (tn1, tn2) in zip(tn1s, tn2s) - v = only(vertices(tn2)) - res += tn1[v] * tn2[v] + ::Algorithm"fit", + tns::Vector{<:Tuple{<:AbstractTTN, <:AbstractTTN}}; + init, + nsites = 2, + nsweeps = 1, + cutoff = eps(), + updater = contract_updater, + kwargs..., + ) + tn1s = first.(tns) + tn2s = last.(tns) + ns = nv.(tn1s) + n = first(ns) + any(ns .!= nv.(tn2s)) && throw( + DimensionMismatch("Number of sites operator ($n) and state ($(nv(tn2))) do not match") + ) + any(ns .!= n) && + throw(DimensionMismatch("Number of sites in different operators ($n) do not match")) + # ToDo: Write test for single-vertex ttn, this implementation has not been tested. + if n == 1 + res = 0 + for (tn1, tn2) in zip(tn1s, tn2s) + v = only(vertices(tn2)) + res += tn1[v] * tn2[v] + end + return typeof(tn2)([res]) end - return typeof(tn2)([res]) - end - # In case `tn1` and `tn2` have the same internal indices - operator = ProjOuterProdTTN{vertextype(first(tn1s))}[] - for (tn1, tn2) in zip(tn1s, tn2s) - tn1 = sim(linkinds, tn1) + # In case `tn1` and `tn2` have the same internal indices + operator = ProjOuterProdTTN{vertextype(first(tn1s))}[] + for (tn1, tn2) in zip(tn1s, tn2s) + tn1 = sim(linkinds, tn1) - # In case `init` and `tn2` have the same internal indices - init = sim(linkinds, init) - push!(operator, ProjOuterProdTTN(tn2, tn1)) - end - operator = isone(length(operator)) ? only(operator) : ProjTTNSum(operator) - #ToDo: remove? - # Fix site and link inds of init - ## init = deepcopy(init) - ## init = sim(linkinds, init) - ## for v in vertices(tn2) - ## replaceinds!( - ## init[v], siteinds(init, v), uniqueinds(siteinds(tn1, v), siteinds(tn2, v)) - ## ) - ## end + # In case `init` and `tn2` have the same internal indices + init = sim(linkinds, init) + push!(operator, ProjOuterProdTTN(tn2, tn1)) + end + operator = isone(length(operator)) ? only(operator) : ProjTTNSum(operator) + #ToDo: remove? + # Fix site and link inds of init + ## init = deepcopy(init) + ## init = sim(linkinds, init) + ## for v in vertices(tn2) + ## replaceinds!( + ## init[v], siteinds(init, v), uniqueinds(siteinds(tn1, v), siteinds(tn2, v)) + ## ) + ## end - return alternating_update(operator, init; nsweeps, nsites, updater, cutoff, kwargs...) + return alternating_update(operator, init; nsweeps, nsites, updater, cutoff, kwargs...) end function NDTensors.contract( - a::Algorithm"fit", tn1::AbstractTTN, tn2::AbstractTTN; kwargs... -) - return sum_contract(a, [(tn1, tn2)]; kwargs...) + a::Algorithm"fit", tn1::AbstractTTN, tn2::AbstractTTN; kwargs... + ) + return sum_contract(a, [(tn1, tn2)]; kwargs...) end """ Overload of `ITensors.contract`. """ -function NDTensors.contract(tn1::AbstractTTN, tn2::AbstractTTN; alg="fit", kwargs...) - return contract(Algorithm(alg), tn1, tn2; kwargs...) +function NDTensors.contract(tn1::AbstractTTN, tn2::AbstractTTN; alg = "fit", kwargs...) + return contract(Algorithm(alg), tn1, tn2; kwargs...) end """ Overload of `ITensors.apply`. """ function ITensors.apply(tn1::AbstractTTN, tn2::AbstractTTN; init, kwargs...) - init = init' - tn12 = contract(tn1, tn2; init, kwargs...) - return replaceprime(tn12, 1 => 0) + init = init' + tn12 = contract(tn1, tn2; init, kwargs...) + return replaceprime(tn12, 1 => 0) end function sum_apply( - tns::Vector{<:Tuple{<:AbstractTTN,<:AbstractTTN}}; alg="fit", init, kwargs... -) - init = init' - tn12 = sum_contract(Algorithm(alg), tns; init, kwargs...) - return replaceprime(tn12, 1 => 0) + tns::Vector{<:Tuple{<:AbstractTTN, <:AbstractTTN}}; alg = "fit", init, kwargs... + ) + init = init' + tn12 = sum_contract(Algorithm(alg), tns; init, kwargs...) + return replaceprime(tn12, 1 => 0) end diff --git a/src/solvers/defaults.jl b/src/solvers/defaults.jl index 5f25e087..c5cc0a7d 100644 --- a/src/solvers/defaults.jl +++ b/src/solvers/defaults.jl @@ -11,56 +11,56 @@ format(x) = @sprintf("%s", x) format(x::AbstractFloat) = @sprintf("%.1E", x) function default_region_printer(; - inserter_kwargs, - outputlevel, - state, - sweep_plan, - spec, - which_region_update, - which_sweep, - kwargs..., -) - if outputlevel >= 2 - region = first(sweep_plan[which_region_update]) - @printf("Sweep %d, region=%s \n", which_sweep, region) - print(" Truncated using") - for key in [:cutoff, :maxdim, :mindim] - if haskey(inserter_kwargs, key) - print(" ", key, "=", format(inserter_kwargs[key])) - end + inserter_kwargs, + outputlevel, + state, + sweep_plan, + spec, + which_region_update, + which_sweep, + kwargs..., + ) + return if outputlevel >= 2 + region = first(sweep_plan[which_region_update]) + @printf("Sweep %d, region=%s \n", which_sweep, region) + print(" Truncated using") + for key in [:cutoff, :maxdim, :mindim] + if haskey(inserter_kwargs, key) + print(" ", key, "=", format(inserter_kwargs[key])) + end + end + println() + if spec != nothing + @printf( + " Trunc. err=%.2E, bond dimension %d\n", + spec.truncerr, + linkdim(state, edgetype(state)(region...)) + ) + end + flush(stdout) end - println() - if spec != nothing - @printf( - " Trunc. err=%.2E, bond dimension %d\n", - spec.truncerr, - linkdim(state, edgetype(state)(region...)) - ) - end - flush(stdout) - end end #ToDo: Implement sweep_time_printer more generally #ToDo: Implement more printers #ToDo: Move to another file? function default_sweep_time_printer(; outputlevel, which_sweep, kwargs...) - if outputlevel >= 1 - sweeps_per_step = order ÷ 2 - if which_sweep % sweeps_per_step == 0 - current_time = (which_sweep / sweeps_per_step) * time_step - println("Current time (sweep $which_sweep) = ", round(current_time; digits=3)) + if outputlevel >= 1 + sweeps_per_step = order ÷ 2 + if which_sweep % sweeps_per_step == 0 + current_time = (which_sweep / sweeps_per_step) * time_step + println("Current time (sweep $which_sweep) = ", round(current_time; digits = 3)) + end end - end - return nothing + return nothing end function default_sweep_printer(; outputlevel, state, which_sweep, sweep_time, kwargs...) - if outputlevel >= 1 - print("After sweep ", which_sweep, ":") - print(" maxlinkdim=", maxlinkdim(state)) - print(" cpu_time=", round(sweep_time; digits=3)) - println() - flush(stdout) - end + return if outputlevel >= 1 + print("After sweep ", which_sweep, ":") + print(" maxlinkdim=", maxlinkdim(state)) + print(" cpu_time=", round(sweep_time; digits = 3)) + println() + flush(stdout) + end end diff --git a/src/solvers/dmrg.jl b/src/solvers/dmrg.jl index 1acbde35..8afc64f6 100644 --- a/src/solvers/dmrg.jl +++ b/src/solvers/dmrg.jl @@ -1,23 +1,23 @@ using KrylovKit: KrylovKit function dmrg( - operator, - init_state; - nsweeps, - nsites=2, - updater=eigsolve_updater, - (region_observer!)=nothing, - kwargs..., -) - eigvals_ref = Ref{Any}() - region_observer! = compose_observers( - region_observer!, ValuesObserver((; eigvals=eigvals_ref)) - ) - state = alternating_update( - operator, init_state; nsweeps, nsites, updater, region_observer!, kwargs... - ) - eigval = only(eigvals_ref[]) - return eigval, state + operator, + init_state; + nsweeps, + nsites = 2, + updater = eigsolve_updater, + (region_observer!) = nothing, + kwargs..., + ) + eigvals_ref = Ref{Any}() + region_observer! = compose_observers( + region_observer!, ValuesObserver((; eigvals = eigvals_ref)) + ) + state = alternating_update( + operator, init_state; nsweeps, nsites, updater, region_observer!, kwargs... + ) + eigval = only(eigvals_ref[]) + return eigval, state end KrylovKit.eigsolve(H, init::AbstractTTN; kwargs...) = dmrg(H, init; kwargs...) diff --git a/src/solvers/dmrg_x.jl b/src/solvers/dmrg_x.jl index 7ab9d8cd..ba3bf747 100644 --- a/src/solvers/dmrg_x.jl +++ b/src/solvers/dmrg_x.jl @@ -1,19 +1,19 @@ function dmrg_x( - operator, - init_state::AbstractTTN; - nsweeps, - nsites=2, - updater=dmrg_x_updater, - (region_observer!)=nothing, - kwargs..., -) - eigvals_ref = Ref{Any}() - region_observer! = compose_observers( - region_observer!, ValuesObserver((; eigvals=eigvals_ref)) - ) - state = alternating_update( - operator, init_state; nsweeps, nsites, updater, region_observer!, kwargs... - ) - eigval = only(eigvals_ref[]) - return eigval, state + operator, + init_state::AbstractTTN; + nsweeps, + nsites = 2, + updater = dmrg_x_updater, + (region_observer!) = nothing, + kwargs..., + ) + eigvals_ref = Ref{Any}() + region_observer! = compose_observers( + region_observer!, ValuesObserver((; eigvals = eigvals_ref)) + ) + state = alternating_update( + operator, init_state; nsweeps, nsites, updater, region_observer!, kwargs... + ) + eigval = only(eigvals_ref[]) + return eigval, state end diff --git a/src/solvers/extract/extract.jl b/src/solvers/extract/extract.jl index 1013d1bd..26c79d20 100644 --- a/src/solvers/extract/extract.jl +++ b/src/solvers/extract/extract.jl @@ -1,28 +1,28 @@ # Here extract_local_tensor and insert_local_tensor -# are essentially inverse operations, adapted for different kinds of +# are essentially inverse operations, adapted for different kinds of # algorithms and networks. # # In the simplest case, exact_local_tensor contracts together a few -# tensors of the network and returns the result, while +# tensors of the network and returns the result, while # insert_local_tensors takes that tensor and factorizes it back # apart and puts it back into the network. # function default_extracter(state, projected_operator, region; internal_kwargs) - if isa(region, AbstractEdge) - # TODO: add functionality for orthogonalizing onto a bond so that can be called instead - vsrc, vdst = src(region), dst(region) - state = orthogonalize(state, vsrc) - left_inds = uniqueinds(state[vsrc], state[vdst]) - U, S, V = svd( - state[vsrc], left_inds; lefttags=tags(state, region), righttags=tags(state, region) - ) - state[vsrc] = U - local_tensor = S * V - else - state = orthogonalize(state, region) - local_tensor = prod(state[v] for v in region) - end - projected_operator = position(projected_operator, state, region) - return state, projected_operator, local_tensor + if isa(region, AbstractEdge) + # TODO: add functionality for orthogonalizing onto a bond so that can be called instead + vsrc, vdst = src(region), dst(region) + state = orthogonalize(state, vsrc) + left_inds = uniqueinds(state[vsrc], state[vdst]) + U, S, V = svd( + state[vsrc], left_inds; lefttags = tags(state, region), righttags = tags(state, region) + ) + state[vsrc] = U + local_tensor = S * V + else + state = orthogonalize(state, region) + local_tensor = prod(state[v] for v in region) + end + projected_operator = position(projected_operator, state, region) + return state, projected_operator, local_tensor end diff --git a/src/solvers/insert/insert.jl b/src/solvers/insert/insert.jl index 01fb35bd..a59ce3be 100644 --- a/src/solvers/insert/insert.jl +++ b/src/solvers/insert/insert.jl @@ -1,46 +1,46 @@ # Here extract_local_tensor and insert_local_tensor -# are essentially inverse operations, adapted for different kinds of +# are essentially inverse operations, adapted for different kinds of # algorithms and networks. # TODO: use dense TTN constructor to make this more general. function default_inserter( - state::AbstractTTN, - phi::ITensor, - region; - normalize=false, - maxdim=nothing, - mindim=nothing, - cutoff=nothing, - internal_kwargs, -) - state = copy(state) - spec = nothing - if length(region) == 2 - v = last(region) - e = edgetype(state)(first(region), last(region)) - indsTe = inds(state[first(region)]) - L, phi, spec = factorize(phi, indsTe; tags=tags(state, e), maxdim, mindim, cutoff) - state[first(region)] = L - else - v = only(region) - end - state[v] = phi - state = set_ortho_region(state, [v]) - normalize && (state[v] /= norm(state[v])) - return state, spec + state::AbstractTTN, + phi::ITensor, + region; + normalize = false, + maxdim = nothing, + mindim = nothing, + cutoff = nothing, + internal_kwargs, + ) + state = copy(state) + spec = nothing + if length(region) == 2 + v = last(region) + e = edgetype(state)(first(region), last(region)) + indsTe = inds(state[first(region)]) + L, phi, spec = factorize(phi, indsTe; tags = tags(state, e), maxdim, mindim, cutoff) + state[first(region)] = L + else + v = only(region) + end + state[v] = phi + state = set_ortho_region(state, [v]) + normalize && (state[v] /= norm(state[v])) + return state, spec end function default_inserter( - state::AbstractTTN, - phi::ITensor, - region::NamedEdge; - cutoff=nothing, - maxdim=nothing, - mindim=nothing, - normalize=false, - internal_kwargs, -) - state[dst(region)] *= phi - state = set_ortho_region(state, [dst(region)]) - return state, nothing + state::AbstractTTN, + phi::ITensor, + region::NamedEdge; + cutoff = nothing, + maxdim = nothing, + mindim = nothing, + normalize = false, + internal_kwargs, + ) + state[dst(region)] *= phi + state = set_ortho_region(state, [dst(region)]) + return state, nothing end diff --git a/src/solvers/linsolve.jl b/src/solvers/linsolve.jl index acd93cef..9338980b 100644 --- a/src/solvers/linsolve.jl +++ b/src/solvers/linsolve.jl @@ -25,25 +25,25 @@ Keyword arguments: Overload of `KrylovKit.linsolve`. """ function KrylovKit.linsolve( - A::AbstractTTN, - b::AbstractTTN, - x₀::AbstractTTN, - a₀::Number=0, - a₁::Number=1; - updater=linsolve_updater, - nsites=2, - nsweeps, #it makes sense to require this to be defined - updater_kwargs=(;), - kwargs..., -) - updater_kwargs = (; a₀, a₁, updater_kwargs...) - error("`linsolve` for TTN not yet implemented.") - - # TODO: Define `itensornetwork_cache` - # TODO: Define `linsolve_cache` - - P = linsolve_cache(itensornetwork_cache(x₀', A, x₀), itensornetwork_cache(x₀', b)) - return alternating_update( - P, x₀; nsweeps, nsites, updater=linsolve_updater, updater_kwargs, kwargs... - ) + A::AbstractTTN, + b::AbstractTTN, + x₀::AbstractTTN, + a₀::Number = 0, + a₁::Number = 1; + updater = linsolve_updater, + nsites = 2, + nsweeps, #it makes sense to require this to be defined + updater_kwargs = (;), + kwargs..., + ) + updater_kwargs = (; a₀, a₁, updater_kwargs...) + error("`linsolve` for TTN not yet implemented.") + + # TODO: Define `itensornetwork_cache` + # TODO: Define `linsolve_cache` + + P = linsolve_cache(itensornetwork_cache(x₀', A, x₀), itensornetwork_cache(x₀', b)) + return alternating_update( + P, x₀; nsweeps, nsites, updater = linsolve_updater, updater_kwargs, kwargs... + ) end diff --git a/src/solvers/local_solvers/contract.jl b/src/solvers/local_solvers/contract.jl index bffefdef..65ddcb82 100644 --- a/src/solvers/local_solvers/contract.jl +++ b/src/solvers/local_solvers/contract.jl @@ -1,13 +1,13 @@ function contract_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - internal_kwargs, -) - P = projected_operator![] - return contract_ket(P, ITensor(one(Bool))), (;) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + internal_kwargs, + ) + P = projected_operator![] + return contract_ket(P, ITensor(one(Bool))), (;) end diff --git a/src/solvers/local_solvers/dmrg_x.jl b/src/solvers/local_solvers/dmrg_x.jl index 3c3ae429..bb079c00 100644 --- a/src/solvers/local_solvers/dmrg_x.jl +++ b/src/solvers/local_solvers/dmrg_x.jl @@ -3,20 +3,20 @@ using ITensors.NDTensors: array using LinearAlgebra: eigen function dmrg_x_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - internal_kwargs, -) - H = contract(projected_operator![], ITensor(true)) - D, U = eigen(H; ishermitian=true) - u = uniqueind(U, H) - max_overlap, max_ind = findmax(abs, array(dag(init) * U)) - U_max = U * dag(onehot(u => max_ind)) - eigvals = [((onehot(u => max_ind)' * D) * dag(onehot(u => max_ind)))[]] - return U_max, (; eigvals) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + internal_kwargs, + ) + H = contract(projected_operator![], ITensor(true)) + D, U = eigen(H; ishermitian = true) + u = uniqueind(U, H) + max_overlap, max_ind = findmax(abs, array(dag(init) * U)) + U_max = U * dag(onehot(u => max_ind)) + eigvals = [((onehot(u => max_ind)' * D) * dag(onehot(u => max_ind)))[]] + return U_max, (; eigvals) end diff --git a/src/solvers/local_solvers/eigsolve.jl b/src/solvers/local_solvers/eigsolve.jl index ed993d80..d85e2ae4 100644 --- a/src/solvers/local_solvers/eigsolve.jl +++ b/src/solvers/local_solvers/eigsolve.jl @@ -1,34 +1,34 @@ using KrylovKit: eigsolve function eigsolve_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - internal_kwargs, - which_eigval=:SR, - ishermitian=true, - tol=1e-14, - krylovdim=3, - maxiter=1, - verbosity=0, - eager=false, -) - howmany = 1 - vals, vecs, info = eigsolve( - projected_operator![], - init, - howmany, - which_eigval; - ishermitian, - tol, - krylovdim, - maxiter, - verbosity, - eager, - ) - return vecs[1], (; info, eigvals=vals) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + internal_kwargs, + which_eigval = :SR, + ishermitian = true, + tol = 1.0e-14, + krylovdim = 3, + maxiter = 1, + verbosity = 0, + eager = false, + ) + howmany = 1 + vals, vecs, info = eigsolve( + projected_operator![], + init, + howmany, + which_eigval; + ishermitian, + tol, + krylovdim, + maxiter, + verbosity, + eager, + ) + return vecs[1], (; info, eigvals = vals) end diff --git a/src/solvers/local_solvers/exponentiate.jl b/src/solvers/local_solvers/exponentiate.jl index c70a91c5..46057615 100644 --- a/src/solvers/local_solvers/exponentiate.jl +++ b/src/solvers/local_solvers/exponentiate.jl @@ -1,34 +1,34 @@ using KrylovKit: exponentiate function exponentiate_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - internal_kwargs, - krylovdim=30, - maxiter=100, - verbosity=0, - tol=1E-12, - ishermitian=true, - issymmetric=true, - eager=true, -) - (; time_step) = internal_kwargs - result, exp_info = exponentiate( - projected_operator![], - time_step, - init; - eager, - krylovdim, - maxiter, - verbosity, - tol, - ishermitian, - issymmetric, - ) - return result, (; info=exp_info) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + internal_kwargs, + krylovdim = 30, + maxiter = 100, + verbosity = 0, + tol = 1.0e-12, + ishermitian = true, + issymmetric = true, + eager = true, + ) + (; time_step) = internal_kwargs + result, exp_info = exponentiate( + projected_operator![], + time_step, + init; + eager, + krylovdim, + maxiter, + verbosity, + tol, + ishermitian, + issymmetric, + ) + return result, (; info = exp_info) end diff --git a/src/solvers/local_solvers/linsolve.jl b/src/solvers/local_solvers/linsolve.jl index c5b8c4c6..25ffc267 100644 --- a/src/solvers/local_solvers/linsolve.jl +++ b/src/solvers/local_solvers/linsolve.jl @@ -1,26 +1,26 @@ using KrylovKit: linsolve function linsolve_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - region_kwargs, - ishermitian=false, - tol=1E-14, - krylovdim=30, - maxiter=100, - verbosity=0, - a₀, - a₁, -) - P = projected_operator![] - b = dag(only(proj_mps(P))) - x, info = linsolve( - P, b, init, a₀, a₁; ishermitian=false, tol, krylovdim, maxiter, verbosity - ) - return x, (;) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + region_kwargs, + ishermitian = false, + tol = 1.0e-14, + krylovdim = 30, + maxiter = 100, + verbosity = 0, + a₀, + a₁, + ) + P = projected_operator![] + b = dag(only(proj_mps(P))) + x, info = linsolve( + P, b, init, a₀, a₁; ishermitian = false, tol, krylovdim, maxiter, verbosity + ) + return x, (;) end diff --git a/src/solvers/solver_utils.jl b/src/solvers/solver_utils.jl index b898fbd9..77a18bc1 100644 --- a/src/solvers/solver_utils.jl +++ b/src/solvers/solver_utils.jl @@ -15,25 +15,25 @@ https://github.com/JuliaDiff/FiniteDifferences.jl/blob/main/src/to_vec.jl to_vec(x) = error("Not implemented") function to_vec(x::ITensor) - function ITensor_from_vec(x_vec) - return itensor(x_vec, inds(x)) - end - return vec(array(x)), ITensor_from_vec + function ITensor_from_vec(x_vec) + return itensor(x_vec, inds(x)) + end + return vec(array(x)), ITensor_from_vec end # Represents a time-dependent sum of terms: # # H(t) = f[1](t) * H0[1] + f[2](t) * H0[2] + … # -struct TimeDependentSum{S,T} - f::Vector{S} - H0::T +struct TimeDependentSum{S, T} + f::Vector{S} + H0::T end TimeDependentSum(f::Vector, H0::ProjTTNSum) = TimeDependentSum(f, terms(H0)) Base.length(H::TimeDependentSum) = length(H.f) function Base.:*(c::Number, H::TimeDependentSum) - return TimeDependentSum([t -> c * fₙ(t) for fₙ in H.f], H.H0) + return TimeDependentSum([t -> c * fₙ(t) for fₙ in H.f], H.H0) end Base.:*(H::TimeDependentSum, c::Number) = c * H @@ -48,9 +48,9 @@ Base.:*(H::TimeDependentSum, c::Number) = c * H # # H = coefficient[1] * H[1] + coefficient * H[2] + … # -struct ScaledSum{S,T} - coefficients::Vector{S} - H::T +struct ScaledSum{S, T} + coefficients::Vector{S} + H::T end Base.length(H::ScaledSum) = length(H.coefficients) @@ -60,30 +60,30 @@ Base.length(H::ScaledSum) = length(H.coefficients) # # onto ψ₀. function (H::ScaledSum)(ψ₀) - ψ = ITensor(inds(ψ₀)) - for n in 1:length(H) - ψ += H.coefficients[n] * apply(H.H[n], ψ₀) - end - return permute(ψ, inds(ψ₀)) + ψ = ITensor(inds(ψ₀)) + for n in 1:length(H) + ψ += H.coefficients[n] * apply(H.H[n], ψ₀) + end + return permute(ψ, inds(ψ₀)) end function cache_operator_to_disk( - state, - operator; - # univeral kwarg signature - outputlevel, - # non-universal kwarg - write_when_maxdim_exceeds, -) - isnothing(write_when_maxdim_exceeds) && return operator - m = maximum(edge_data(linkdims(state))) - if m > write_when_maxdim_exceeds - if outputlevel >= 2 - println( - "write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxlinkdim = $(m), writing environment tensors to disk", - ) + state, + operator; + # univeral kwarg signature + outputlevel, + # non-universal kwarg + write_when_maxdim_exceeds, + ) + isnothing(write_when_maxdim_exceeds) && return operator + m = maximum(edge_data(linkdims(state))) + if m > write_when_maxdim_exceeds + if outputlevel >= 2 + println( + "write_when_maxdim_exceeds = $write_when_maxdim_exceeds and maxlinkdim = $(m), writing environment tensors to disk", + ) + end + operator = disk(operator) end - operator = disk(operator) - end - return operator + return operator end diff --git a/src/solvers/sweep_plans/sweep_plans.jl b/src/solvers/sweep_plans/sweep_plans.jl index dda6dd96..4288931b 100644 --- a/src/solvers/sweep_plans/sweep_plans.jl +++ b/src/solvers/sweep_plans/sweep_plans.jl @@ -4,218 +4,218 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions direction(step_number) = isodd(step_number) ? Base.Forward : Base.Reverse function overlap(edge_a::AbstractEdge, edge_b::AbstractEdge) - return intersect(support(edge_a), support(edge_b)) + return intersect(support(edge_a), support(edge_b)) end function support(edge::AbstractEdge) - return [src(edge), dst(edge)] + return [src(edge), dst(edge)] end support(r) = r -function reverse_region(edges, which_edge; reverse_edge=false, nsites=1, region_kwargs=(;)) - current_edge = edges[which_edge] - if nsites == 1 - !reverse_edge && return [(current_edge, region_kwargs)] - reverse_edge && return [(reverse(current_edge), region_kwargs)] - elseif nsites == 2 - if last(edges) == current_edge - return () +function reverse_region(edges, which_edge; reverse_edge = false, nsites = 1, region_kwargs = (;)) + current_edge = edges[which_edge] + if nsites == 1 + !reverse_edge && return [(current_edge, region_kwargs)] + reverse_edge && return [(reverse(current_edge), region_kwargs)] + elseif nsites == 2 + if last(edges) == current_edge + return () + end + future_edges = edges[(which_edge + 1):end] + future_edges = isa(future_edges, AbstractEdge) ? [future_edges] : future_edges + #error if more than single vertex overlap + overlapping_vertex = only(union([overlap(e, current_edge) for e in future_edges]...)) + return [([overlapping_vertex], region_kwargs)] end - future_edges = edges[(which_edge + 1):end] - future_edges = isa(future_edges, AbstractEdge) ? [future_edges] : future_edges - #error if more than single vertex overlap - overlapping_vertex = only(union([overlap(e, current_edge) for e in future_edges]...)) - return [([overlapping_vertex], region_kwargs)] - end end -function forward_region(edges, which_edge; nsites=1, region_kwargs=(;)) - if nsites == 1 - current_edge = edges[which_edge] - #handle edge case - if current_edge == last(edges) - overlapping_vertex = only( - union([overlap(e, current_edge) for e in edges[1:(which_edge - 1)]]...) - ) - nonoverlapping_vertex = only( - setdiff([src(current_edge), dst(current_edge)], [overlapping_vertex]) - ) - return [ - ([overlapping_vertex], region_kwargs), ([nonoverlapping_vertex], region_kwargs) - ] - else - future_edges = edges[(which_edge + 1):end] - future_edges = isa(future_edges, AbstractEdge) ? [future_edges] : future_edges - overlapping_vertex = only(union([overlap(e, current_edge) for e in future_edges]...)) - nonoverlapping_vertex = only( - setdiff([src(current_edge), dst(current_edge)], [overlapping_vertex]) - ) - return [([nonoverlapping_vertex], region_kwargs)] +function forward_region(edges, which_edge; nsites = 1, region_kwargs = (;)) + if nsites == 1 + current_edge = edges[which_edge] + #handle edge case + if current_edge == last(edges) + overlapping_vertex = only( + union([overlap(e, current_edge) for e in edges[1:(which_edge - 1)]]...) + ) + nonoverlapping_vertex = only( + setdiff([src(current_edge), dst(current_edge)], [overlapping_vertex]) + ) + return [ + ([overlapping_vertex], region_kwargs), ([nonoverlapping_vertex], region_kwargs), + ] + else + future_edges = edges[(which_edge + 1):end] + future_edges = isa(future_edges, AbstractEdge) ? [future_edges] : future_edges + overlapping_vertex = only(union([overlap(e, current_edge) for e in future_edges]...)) + nonoverlapping_vertex = only( + setdiff([src(current_edge), dst(current_edge)], [overlapping_vertex]) + ) + return [([nonoverlapping_vertex], region_kwargs)] + end + elseif nsites == 2 + current_edge = edges[which_edge] + return [([src(current_edge), dst(current_edge)], region_kwargs)] end - elseif nsites == 2 - current_edge = edges[which_edge] - return [([src(current_edge), dst(current_edge)], region_kwargs)] - end end function forward_sweep( - dir::Base.ForwardOrdering, - graph::AbstractGraph; - root_vertex=GraphsExtensions.default_root_vertex(graph), - reverse_edges=false, - region_kwargs, - reverse_kwargs=region_kwargs, - reverse_step=false, - kwargs..., -) - edges = post_order_dfs_edges(graph, root_vertex) - regions = map(eachindex(edges)) do i - forward_region(edges, i; region_kwargs, kwargs...) - end - regions = collect(flatten(regions)) - if reverse_step - reverse_regions = map(eachindex(edges)) do i - reverse_region( - edges, i; reverse_edge=reverse_edges, region_kwargs=reverse_kwargs, kwargs... - ) + dir::Base.ForwardOrdering, + graph::AbstractGraph; + root_vertex = GraphsExtensions.default_root_vertex(graph), + reverse_edges = false, + region_kwargs, + reverse_kwargs = region_kwargs, + reverse_step = false, + kwargs..., + ) + edges = post_order_dfs_edges(graph, root_vertex) + regions = map(eachindex(edges)) do i + forward_region(edges, i; region_kwargs, kwargs...) + end + regions = collect(flatten(regions)) + if reverse_step + reverse_regions = map(eachindex(edges)) do i + reverse_region( + edges, i; reverse_edge = reverse_edges, region_kwargs = reverse_kwargs, kwargs... + ) + end + reverse_regions = collect(flatten(reverse_regions)) + _check_reverse_sweeps(regions, reverse_regions, graph; kwargs...) + regions = interleave(regions, reverse_regions) end - reverse_regions = collect(flatten(reverse_regions)) - _check_reverse_sweeps(regions, reverse_regions, graph; kwargs...) - regions = interleave(regions, reverse_regions) - end - return regions + return regions end #ToDo: is there a better name for this? unidirectional_sweep? traversal? function forward_sweep(dir::Base.ReverseOrdering, args...; kwargs...) - return reverse(forward_sweep(Base.Forward, args...; reverse_edges=true, kwargs...)) + return reverse(forward_sweep(Base.Forward, args...; reverse_edges = true, kwargs...)) end function default_sweep_plans( - nsweeps, - init_state; - sweep_plan_func=default_sweep_plan, - root_vertex, - extracter, - extracter_kwargs, - updater, - updater_kwargs, - inserter, - inserter_kwargs, - transform_operator, - transform_operator_kwargs, - kwargs..., -) - extracter, updater, inserter, transform_operator = extend_or_truncate.( - (extracter, updater, inserter, transform_operator), nsweeps - ) - inserter_kwargs, updater_kwargs, extracter_kwargs, transform_operator_kwargs, kwargs = expand.( - ( - inserter_kwargs, - updater_kwargs, - extracter_kwargs, - transform_operator_kwargs, - NamedTuple(kwargs), - ), - nsweeps, - ) - sweep_plans = [] - for i in 1:nsweeps - sweep_plan = sweep_plan_func( - init_state; - root_vertex, - region_kwargs=(; - inserter=inserter[i], - inserter_kwargs=inserter_kwargs[i], - updater=updater[i], - updater_kwargs=updater_kwargs[i], - extracter=extracter[i], - extracter_kwargs=extracter_kwargs[i], - transform_operator=transform_operator[i], - transform_operator_kwargs=transform_operator_kwargs[i], - ), - kwargs[i]..., + nsweeps, + init_state; + sweep_plan_func = default_sweep_plan, + root_vertex, + extracter, + extracter_kwargs, + updater, + updater_kwargs, + inserter, + inserter_kwargs, + transform_operator, + transform_operator_kwargs, + kwargs..., + ) + extracter, updater, inserter, transform_operator = extend_or_truncate.( + (extracter, updater, inserter, transform_operator), nsweeps ) - push!(sweep_plans, sweep_plan) - end - return sweep_plans + inserter_kwargs, updater_kwargs, extracter_kwargs, transform_operator_kwargs, kwargs = expand.( + ( + inserter_kwargs, + updater_kwargs, + extracter_kwargs, + transform_operator_kwargs, + NamedTuple(kwargs), + ), + nsweeps, + ) + sweep_plans = [] + for i in 1:nsweeps + sweep_plan = sweep_plan_func( + init_state; + root_vertex, + region_kwargs = (; + inserter = inserter[i], + inserter_kwargs = inserter_kwargs[i], + updater = updater[i], + updater_kwargs = updater_kwargs[i], + extracter = extracter[i], + extracter_kwargs = extracter_kwargs[i], + transform_operator = transform_operator[i], + transform_operator_kwargs = transform_operator_kwargs[i], + ), + kwargs[i]..., + ) + push!(sweep_plans, sweep_plan) + end + return sweep_plans end function default_sweep_plan( - graph::AbstractGraph; - root_vertex=GraphsExtensions.default_root_vertex(graph), - region_kwargs, - nsites::Int, -) - return vcat( - [ - forward_sweep( - direction(half), - graph; - root_vertex, - nsites, - region_kwargs=(; internal_kwargs=(; half), region_kwargs...), - ) for half in 1:2 - ]..., - ) + graph::AbstractGraph; + root_vertex = GraphsExtensions.default_root_vertex(graph), + region_kwargs, + nsites::Int, + ) + return vcat( + [ + forward_sweep( + direction(half), + graph; + root_vertex, + nsites, + region_kwargs = (; internal_kwargs = (; half), region_kwargs...), + ) for half in 1:2 + ]..., + ) end function tdvp_sweep_plan( - graph::AbstractGraph; - root_vertex=GraphsExtensions.default_root_vertex(graph), - region_kwargs, - reverse_step=true, - order::Int, - nsites::Int, - time_step::Number, - t_evolved::Number, -) - sweep_plan = [] - for (substep, fac) in enumerate(sub_time_steps(order)) - sub_time_step = time_step * fac - append!( - sweep_plan, - forward_sweep( - direction(substep), - graph; - root_vertex, - nsites, - region_kwargs=(; - internal_kwargs=(; substep, time_step=sub_time_step, t=t_evolved), - region_kwargs..., - ), - reverse_kwargs=(; - internal_kwargs=(; substep, time_step=(-sub_time_step), t=t_evolved), - region_kwargs..., - ), - reverse_step, - ), + graph::AbstractGraph; + root_vertex = GraphsExtensions.default_root_vertex(graph), + region_kwargs, + reverse_step = true, + order::Int, + nsites::Int, + time_step::Number, + t_evolved::Number, ) - end - return sweep_plan + sweep_plan = [] + for (substep, fac) in enumerate(sub_time_steps(order)) + sub_time_step = time_step * fac + append!( + sweep_plan, + forward_sweep( + direction(substep), + graph; + root_vertex, + nsites, + region_kwargs = (; + internal_kwargs = (; substep, time_step = sub_time_step, t = t_evolved), + region_kwargs..., + ), + reverse_kwargs = (; + internal_kwargs = (; substep, time_step = (-sub_time_step), t = t_evolved), + region_kwargs..., + ), + reverse_step, + ), + ) + end + return sweep_plan end #ToDo: Move to test. function _check_reverse_sweeps(forward_sweep, reverse_sweep, graph; nsites, kwargs...) - fw_regions = first.(forward_sweep) - bw_regions = first.(reverse_sweep) - if nsites == 2 - fw_verts = flatten(fw_regions) - bw_verts = flatten(bw_regions) - for v in vertices(graph) - @assert isone(count(isequal(v), fw_verts) - count(isequal(v), bw_verts)) - end - elseif nsites == 1 - fw_verts = flatten(fw_regions) - bw_edges = bw_regions - for v in vertices(graph) - @assert isone(count(isequal(v), fw_verts)) - end - for e in edges(graph) - @assert isone(count(x -> (isequal(x, e) || isequal(x, reverse(e))), bw_edges)) + fw_regions = first.(forward_sweep) + bw_regions = first.(reverse_sweep) + if nsites == 2 + fw_verts = flatten(fw_regions) + bw_verts = flatten(bw_regions) + for v in vertices(graph) + @assert isone(count(isequal(v), fw_verts) - count(isequal(v), bw_verts)) + end + elseif nsites == 1 + fw_verts = flatten(fw_regions) + bw_edges = bw_regions + for v in vertices(graph) + @assert isone(count(isequal(v), fw_verts)) + end + for e in edges(graph) + @assert isone(count(x -> (isequal(x, e) || isequal(x, reverse(e))), bw_edges)) + end end - end - return true + return true end diff --git a/src/solvers/tdvp.jl b/src/solvers/tdvp.jl index 7a58fe1b..86fec32f 100644 --- a/src/solvers/tdvp.jl +++ b/src/solvers/tdvp.jl @@ -2,73 +2,73 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions #ToDo: Cleanup _compute_nsweeps, maybe restrict flexibility to simplify code function _compute_nsweeps(nsweeps::Int, t::Number, time_step::Number) - return error("Cannot specify both nsweeps and time_step in tdvp") + return error("Cannot specify both nsweeps and time_step in tdvp") end function _compute_nsweeps(nsweeps::Nothing, t::Number, time_step::Nothing) - return 1, [t] + return 1, [t] end function _compute_nsweeps(nsweeps::Nothing, t::Number, time_step::Number) - @assert isfinite(time_step) && abs(time_step) > 0.0 - nsweeps = convert(Int, ceil(abs(t / time_step))) - if !(nsweeps * time_step ≈ t) - println("Time that will be reached = nsweeps * time_step = ", nsweeps * time_step) - println("Requested total time t = ", t) - error("Time step $time_step not commensurate with total time t=$t") - end - return nsweeps, extend_or_truncate(time_step, nsweeps) + @assert isfinite(time_step) && abs(time_step) > 0.0 + nsweeps = convert(Int, ceil(abs(t / time_step))) + if !(nsweeps * time_step ≈ t) + println("Time that will be reached = nsweeps * time_step = ", nsweeps * time_step) + println("Requested total time t = ", t) + error("Time step $time_step not commensurate with total time t=$t") + end + return nsweeps, extend_or_truncate(time_step, nsweeps) end function _compute_nsweeps(nsweeps::Int, t::Number, time_step::Nothing) - time_step = extend_or_truncate(t / nsweeps, nsweeps) - return nsweeps, time_step + time_step = extend_or_truncate(t / nsweeps, nsweeps) + return nsweeps, time_step end function _compute_nsweeps(nsweeps, t::Number, time_step::Vector) - diff_time = t - sum(time_step) + diff_time = t - sum(time_step) - isnothing(nsweeps) - if isnothing(nsweeps) - #extend_or_truncate time_step to reach final time t - last_time_step = last(time_step) - nsweepstopad = Int(ceil(abs(diff_time / last_time_step))) - if !(sum(time_step) + nsweepstopad * last_time_step ≈ t) - println( - "Time that will be reached = nsweeps * time_step = ", - sum(time_step) + nsweepstopad * last_time_step, - ) - println("Requested total time t = ", t) - error("Time step $time_step not commensurate with total time t=$t") - end - time_step = extend_or_truncate(time_step, length(time_step) + nsweepstopad) - nsweeps = length(time_step) - else - nsweepstopad = nsweeps - length(time_step) - if abs(diff_time) < eps() && !iszero(nsweepstopad) - warn( - "A vector of timesteps that sums up to total time t=$t was supplied, + isnothing(nsweeps) + if isnothing(nsweeps) + #extend_or_truncate time_step to reach final time t + last_time_step = last(time_step) + nsweepstopad = Int(ceil(abs(diff_time / last_time_step))) + if !(sum(time_step) + nsweepstopad * last_time_step ≈ t) + println( + "Time that will be reached = nsweeps * time_step = ", + sum(time_step) + nsweepstopad * last_time_step, + ) + println("Requested total time t = ", t) + error("Time step $time_step not commensurate with total time t=$t") + end + time_step = extend_or_truncate(time_step, length(time_step) + nsweepstopad) + nsweeps = length(time_step) + else + nsweepstopad = nsweeps - length(time_step) + if abs(diff_time) < eps() && !iszero(nsweepstopad) + warn( + "A vector of timesteps that sums up to total time t=$t was supplied, but its length (=$(length(time_step))) does not agree with supplied number of sweeps (=$(nsweeps)).", - ) - return length(time_step), time_step + ) + return length(time_step), time_step + end + remaining_time_step = diff_time / nsweepstopad + append!(time_step, extend_or_truncate(remaining_time_step, nsweepstopad)) end - remaining_time_step = diff_time / nsweepstopad - append!(time_step, extend_or_truncate(remaining_time_step, nsweepstopad)) - end - return nsweeps, time_step + return nsweeps, time_step end function sub_time_steps(order) - if order == 1 - return [1.0] - elseif order == 2 - return [1 / 2, 1 / 2] - elseif order == 4 - s = 1.0 / (2 - 2^(1 / 3)) - return [s / 2, s / 2, (1 - 2 * s) / 2, (1 - 2 * s) / 2, s / 2, s / 2] - else - error("Trotter order of $order not supported") - end + if order == 1 + return [1.0] + elseif order == 2 + return [1 / 2, 1 / 2] + elseif order == 4 + s = 1.0 / (2 - 2^(1 / 3)) + return [s / 2, s / 2, (1 - 2 * s) / 2, (1 - 2 * s) / 2, s / 2, s / 2] + else + error("Trotter order of $order not supported") + end end """ @@ -90,65 +90,65 @@ Optional keyword arguments: * `write_when_maxdim_exceeds::Int` - when the allowed maxdim exceeds this value, begin saving tensors to disk to free memory in large calculations """ function tdvp( - operator, - t::Number, - init_state::AbstractTTN; - t_start=0.0, - time_step=nothing, - nsites=2, - nsweeps=nothing, - order::Integer=2, - outputlevel=default_outputlevel(), - region_printer=nothing, - sweep_printer=nothing, - (sweep_observer!)=nothing, - (region_observer!)=nothing, - root_vertex=GraphsExtensions.default_root_vertex(init_state), - reverse_step=true, - extracter_kwargs=(;), - extracter=default_extracter(), # ToDo: extracter could be inside extracter_kwargs, at the cost of having to extract it in region_update - updater_kwargs=(;), - updater=exponentiate_updater, - inserter_kwargs=(;), - inserter=default_inserter(), - transform_operator_kwargs=(;), - transform_operator=default_transform_operator(), - kwargs..., -) - # move slurped kwargs into inserter - inserter_kwargs = (; inserter_kwargs..., kwargs...) - # process nsweeps and time_step - nsweeps, time_step = _compute_nsweeps(nsweeps, t, time_step) - t_evolved = t_start .+ cumsum(time_step) - sweep_plans = default_sweep_plans( - nsweeps, - init_state; - sweep_plan_func=tdvp_sweep_plan, - root_vertex, - reverse_step, - extracter, - extracter_kwargs, - updater, - updater_kwargs, - inserter, - inserter_kwargs, - transform_operator, - transform_operator_kwargs, - time_step, - order, - nsites, - t_evolved, - ) + operator, + t::Number, + init_state::AbstractTTN; + t_start = 0.0, + time_step = nothing, + nsites = 2, + nsweeps = nothing, + order::Integer = 2, + outputlevel = default_outputlevel(), + region_printer = nothing, + sweep_printer = nothing, + (sweep_observer!) = nothing, + (region_observer!) = nothing, + root_vertex = GraphsExtensions.default_root_vertex(init_state), + reverse_step = true, + extracter_kwargs = (;), + extracter = default_extracter(), # ToDo: extracter could be inside extracter_kwargs, at the cost of having to extract it in region_update + updater_kwargs = (;), + updater = exponentiate_updater, + inserter_kwargs = (;), + inserter = default_inserter(), + transform_operator_kwargs = (;), + transform_operator = default_transform_operator(), + kwargs..., + ) + # move slurped kwargs into inserter + inserter_kwargs = (; inserter_kwargs..., kwargs...) + # process nsweeps and time_step + nsweeps, time_step = _compute_nsweeps(nsweeps, t, time_step) + t_evolved = t_start .+ cumsum(time_step) + sweep_plans = default_sweep_plans( + nsweeps, + init_state; + sweep_plan_func = tdvp_sweep_plan, + root_vertex, + reverse_step, + extracter, + extracter_kwargs, + updater, + updater_kwargs, + inserter, + inserter_kwargs, + transform_operator, + transform_operator_kwargs, + time_step, + order, + nsites, + t_evolved, + ) - return alternating_update( - operator, - init_state, - sweep_plans; - outputlevel, - sweep_observer!, - region_observer!, - sweep_printer, - region_printer, - ) - return state + return alternating_update( + operator, + init_state, + sweep_plans; + outputlevel, + sweep_observer!, + region_observer!, + sweep_printer, + region_printer, + ) + return state end diff --git a/src/specialitensornetworks.jl b/src/specialitensornetworks.jl index ba6f196d..507e5c32 100644 --- a/src/specialitensornetworks.jl +++ b/src/specialitensornetworks.jl @@ -9,62 +9,62 @@ RETURN A TENSOR NETWORK WITH COPY TENSORS ON EACH VERTEX. Note that passing a link_space will mean the indices of the resulting network don't match those of the input indsnetwork """ function delta_network(eltype::Type, s::IndsNetwork; kwargs...) - return ITensorNetwork(s; kwargs...) do v - return inds -> delta(eltype, inds) - end + return ITensorNetwork(s; kwargs...) do v + return inds -> delta(eltype, inds) + end end function delta_network(s::IndsNetwork; kwargs...) - return delta_network(Float64, s; kwargs...) + return delta_network(Float64, s; kwargs...) end function delta_network(eltype::Type, graph::AbstractNamedGraph; kwargs...) - return delta_network(eltype, IndsNetwork(graph; kwargs...)) + return delta_network(eltype, IndsNetwork(graph; kwargs...)) end function delta_network(graph::AbstractNamedGraph; kwargs...) - return delta_network(Float64, graph; kwargs...) + return delta_network(Float64, graph; kwargs...) end """ Build an ITensor network on a graph specified by the inds network s. Bond_dim is given by link_space and entries are randomised (normal distribution, mean 0 std 1) """ function random_tensornetwork( - rng::AbstractRNG, eltype::Type, s::IndsNetwork; link_space=1, kwargs... -) - return ITensorNetwork(s; link_space, kwargs...) do v - return inds -> itensor(randn(rng, eltype, dim.(inds)...), inds) - end + rng::AbstractRNG, eltype::Type, s::IndsNetwork; link_space = 1, kwargs... + ) + return ITensorNetwork(s; link_space, kwargs...) do v + return inds -> itensor(randn(rng, eltype, dim.(inds)...), inds) + end end function random_tensornetwork(eltype::Type, s::IndsNetwork; kwargs...) - return random_tensornetwork(Random.default_rng(), eltype, s; kwargs...) + return random_tensornetwork(Random.default_rng(), eltype, s; kwargs...) end function random_tensornetwork(rng::AbstractRNG, s::IndsNetwork; kwargs...) - return random_tensornetwork(rng, Float64, s; kwargs...) + return random_tensornetwork(rng, Float64, s; kwargs...) end function random_tensornetwork(s::IndsNetwork; kwargs...) - return random_tensornetwork(Random.default_rng(), s; kwargs...) + return random_tensornetwork(Random.default_rng(), s; kwargs...) end @traitfn function random_tensornetwork( - rng::AbstractRNG, eltype::Type, g::::IsUnderlyingGraph; kwargs... -) - return random_tensornetwork(rng, eltype, IndsNetwork(g); kwargs...) + rng::AbstractRNG, eltype::Type, g::::IsUnderlyingGraph; kwargs... + ) + return random_tensornetwork(rng, eltype, IndsNetwork(g); kwargs...) end @traitfn function random_tensornetwork(eltype::Type, g::::IsUnderlyingGraph; kwargs...) - return random_tensornetwork(Random.default_rng(), eltype, g; kwargs...) + return random_tensornetwork(Random.default_rng(), eltype, g; kwargs...) end @traitfn function random_tensornetwork(rng::AbstractRNG, g::::IsUnderlyingGraph; kwargs...) - return random_tensornetwork(rng, Float64, g; kwargs...) + return random_tensornetwork(rng, Float64, g; kwargs...) end @traitfn function random_tensornetwork(g::::IsUnderlyingGraph; kwargs...) - return random_tensornetwork(Random.default_rng(), g; kwargs...) + return random_tensornetwork(Random.default_rng(), g; kwargs...) end """ @@ -73,25 +73,25 @@ Bond_dim is given by link_space and entries are randomized. The random distribution is based on the input argument `distribution`. """ function random_tensornetwork( - rng::AbstractRNG, distribution::Distribution, s::IndsNetwork; kwargs... -) - return ITensorNetwork(s; kwargs...) do v - return inds -> itensor(rand(rng, distribution, dim.(inds)...), inds) - end + rng::AbstractRNG, distribution::Distribution, s::IndsNetwork; kwargs... + ) + return ITensorNetwork(s; kwargs...) do v + return inds -> itensor(rand(rng, distribution, dim.(inds)...), inds) + end end function random_tensornetwork(distribution::Distribution, s::IndsNetwork; kwargs...) - return random_tensornetwork(Random.default_rng(), distribution, s; kwargs...) + return random_tensornetwork(Random.default_rng(), distribution, s; kwargs...) end @traitfn function random_tensornetwork( - rng::AbstractRNG, distribution::Distribution, g::::IsUnderlyingGraph; kwargs... -) - return random_tensornetwork(rng, distribution, IndsNetwork(g); kwargs...) + rng::AbstractRNG, distribution::Distribution, g::::IsUnderlyingGraph; kwargs... + ) + return random_tensornetwork(rng, distribution, IndsNetwork(g); kwargs...) end @traitfn function random_tensornetwork( - distribution::Distribution, g::::IsUnderlyingGraph; kwargs... -) - return random_tensornetwork(Random.default_rng(), distribution, g; kwargs...) + distribution::Distribution, g::::IsUnderlyingGraph; kwargs... + ) + return random_tensornetwork(Random.default_rng(), distribution, g; kwargs...) end diff --git a/src/tebd.jl b/src/tebd.jl index d1d96017..c8306bb6 100644 --- a/src/tebd.jl +++ b/src/tebd.jl @@ -1,31 +1,31 @@ using ITensors: Trotter function tebd( - ℋ::Sum, - ψ::AbstractITensorNetwork; - β, - Δβ, - maxdim, - cutoff, - print_frequency=10, - ortho=false, - kwargs..., -) - 𝒰 = exp(-Δβ * ℋ; alg=Trotter{2}()) - # Imaginary time evolution terms - s = siteinds(ψ) - u⃗ = Vector{ITensor}(𝒰, s) - nsteps = Int(β ÷ Δβ) - for step in 1:nsteps - if step % print_frequency == 0 - @show step, (step - 1) * Δβ, β + ℋ::Sum, + ψ::AbstractITensorNetwork; + β, + Δβ, + maxdim, + cutoff, + print_frequency = 10, + ortho = false, + kwargs..., + ) + 𝒰 = exp(-Δβ * ℋ; alg = Trotter{2}()) + # Imaginary time evolution terms + s = siteinds(ψ) + u⃗ = Vector{ITensor}(𝒰, s) + nsteps = Int(β ÷ Δβ) + for step in 1:nsteps + if step % print_frequency == 0 + @show step, (step - 1) * Δβ, β + end + ψ = insert_linkinds(ψ) + ψ = apply(u⃗, ψ; cutoff, maxdim, normalize = true, ortho, kwargs...) + if ortho + for v in vertices(ψ) + ψ = tree_orthogonalize(ψ, v) + end + end end - ψ = insert_linkinds(ψ) - ψ = apply(u⃗, ψ; cutoff, maxdim, normalize=true, ortho, kwargs...) - if ortho - for v in vertices(ψ) - ψ = tree_orthogonalize(ψ, v) - end - end - end - return ψ + return ψ end diff --git a/src/treetensornetworks/abstracttreetensornetwork.jl b/src/treetensornetworks/abstracttreetensornetwork.jl index 5d57bb46..7368d430 100644 --- a/src/treetensornetworks/abstracttreetensornetwork.jl +++ b/src/treetensornetworks/abstracttreetensornetwork.jl @@ -1,11 +1,11 @@ using Graphs: has_vertex using NamedGraphs.GraphsExtensions: - GraphsExtensions, - edge_path, - leaf_vertices, - post_order_dfs_edges, - post_order_dfs_vertices, - a_star + GraphsExtensions, + edge_path, + leaf_vertices, + post_order_dfs_edges, + post_order_dfs_vertices, + a_star using NamedGraphs: namedgraph_a_star, steiner_tree using IsApprox: IsApprox, Approx using ITensors: ITensors, Algorithm, @Algorithm_str, directsum, hasinds, permute, plev @@ -16,60 +16,60 @@ abstract type AbstractTreeTensorNetwork{V} <: AbstractITensorNetwork{V} end const AbstractTTN = AbstractTreeTensorNetwork function DataGraphs.underlying_graph_type(G::Type{<:AbstractTTN}) - return underlying_graph_type(data_graph_type(G)) + return underlying_graph_type(data_graph_type(G)) end -# +# # Field access -# +# ITensorNetwork(tn::AbstractTTN) = error("Not implemented") ortho_region(tn::AbstractTTN) = error("Not implemented") -# +# # Orthogonality center -# +# function set_ortho_region(tn::AbstractTTN, new_region) - return error("Not implemented") + return error("Not implemented") end function gauge(alg::Algorithm, ttn::AbstractTTN, region::Vector; kwargs...) - ttn = tree_gauge(alg, ttn, collect(ortho_region(ttn)), region; kwargs...) - return set_ortho_region(ttn, region) + ttn = tree_gauge(alg, ttn, collect(ortho_region(ttn)), region; kwargs...) + return set_ortho_region(ttn, region) end function gauge(alg::Algorithm, ttn::AbstractTTN, region; kwargs...) - return gauge(alg, ttn, [region]; kwargs...) + return gauge(alg, ttn, [region]; kwargs...) end function orthogonalize(ttn::AbstractTTN, region; kwargs...) - return gauge(Algorithm("orthogonalize"), ttn, region; kwargs...) + return gauge(Algorithm("orthogonalize"), ttn, region; kwargs...) end function tree_orthogonalize(ttn::AbstractTTN, args...; kwargs...) - return orthogonalize(ttn, args...; kwargs...) + return orthogonalize(ttn, args...; kwargs...) end -# +# # Truncation -# +# function Base.truncate( - tn::AbstractTTN; root_vertex=GraphsExtensions.default_root_vertex(tn), kwargs... -) - for e in post_order_dfs_edges(tn, root_vertex) - # always orthogonalize towards source first to make truncations controlled - tn = orthogonalize(tn, src(e)) - tn = truncate(tn, e; kwargs...) - tn = set_ortho_region(tn, typeof(ortho_region(tn))([dst(e)])) - end - return tn + tn::AbstractTTN; root_vertex = GraphsExtensions.default_root_vertex(tn), kwargs... + ) + for e in post_order_dfs_edges(tn, root_vertex) + # always orthogonalize towards source first to make truncations controlled + tn = orthogonalize(tn, src(e)) + tn = truncate(tn, e; kwargs...) + tn = set_ortho_region(tn, typeof(ortho_region(tn))([dst(e)])) + end + return tn end # For ambiguity error function Base.truncate(tn::AbstractTTN, edge::AbstractEdge; kwargs...) - return typeof(tn)(truncate(ITensorNetwork(tn), edge; kwargs...)) + return typeof(tn)(truncate(ITensorNetwork(tn), edge; kwargs...)) end # @@ -78,76 +78,76 @@ end # TODO: decide on contraction order: reverse dfs vertices or forward dfs edges? function NDTensors.contract( - tn::AbstractTTN, root_vertex=GraphsExtensions.default_root_vertex(tn); kwargs... -) - tn = copy(tn) - # reverse post order vertices - traversal_order = reverse(post_order_dfs_vertices(tn, root_vertex)) - return contract(ITensorNetwork(tn); sequence=traversal_order, kwargs...) - # # forward post order edges - # tn = copy(tn) - # for e in post_order_dfs_edges(tn, root_vertex) - # tn = contract(tn, e) - # end - # return tn[root_vertex] + tn::AbstractTTN, root_vertex = GraphsExtensions.default_root_vertex(tn); kwargs... + ) + tn = copy(tn) + # reverse post order vertices + traversal_order = reverse(post_order_dfs_vertices(tn, root_vertex)) + return contract(ITensorNetwork(tn); sequence = traversal_order, kwargs...) + # # forward post order edges + # tn = copy(tn) + # for e in post_order_dfs_edges(tn, root_vertex) + # tn = contract(tn, e) + # end + # return tn[root_vertex] end function ITensors.inner( - x::AbstractTTN, y::AbstractTTN; root_vertex=GraphsExtensions.default_root_vertex(x) -) - xᴴ = sim(dag(x); sites=[]) - y = sim(y; sites=[]) - xy = xᴴ ⊗ y - # TODO: find the largest tensor and use it as - # the `root_vertex`. - for e in post_order_dfs_edges(y, root_vertex) - if has_vertex(xy, (src(e), 2)) - xy = contract(xy, (src(e), 2) => (src(e), 1)) - end - xy = contract(xy, (src(e), 1) => (dst(e), 1)) - if has_vertex(xy, (dst(e), 2)) - xy = contract(xy, (dst(e), 2) => (dst(e), 1)) + x::AbstractTTN, y::AbstractTTN; root_vertex = GraphsExtensions.default_root_vertex(x) + ) + xᴴ = sim(dag(x); sites = []) + y = sim(y; sites = []) + xy = xᴴ ⊗ y + # TODO: find the largest tensor and use it as + # the `root_vertex`. + for e in post_order_dfs_edges(y, root_vertex) + if has_vertex(xy, (src(e), 2)) + xy = contract(xy, (src(e), 2) => (src(e), 1)) + end + xy = contract(xy, (src(e), 1) => (dst(e), 1)) + if has_vertex(xy, (dst(e), 2)) + xy = contract(xy, (dst(e), 2) => (dst(e), 1)) + end end - end - return xy[root_vertex, 1][] + return xy[root_vertex, 1][] end function LinearAlgebra.norm(tn::AbstractTTN) - if isone(length(ortho_region(tn))) - return norm(tn[only(ortho_region(tn))]) - end - return √(abs(real(inner(tn, tn)))) + if isone(length(ortho_region(tn))) + return norm(tn[only(ortho_region(tn))]) + end + return √(abs(real(inner(tn, tn)))) end -# +# # Utility -# +# function LinearAlgebra.normalize!(tn::AbstractTTN) - c = ortho_region(tn) - lognorm_tn = lognorm(tn) - if lognorm_tn == -Inf + c = ortho_region(tn) + lognorm_tn = lognorm(tn) + if lognorm_tn == -Inf + return tn + end + z = exp(lognorm_tn / length(c)) + for v in c + tn[v] ./= z + end return tn - end - z = exp(lognorm_tn / length(c)) - for v in c - tn[v] ./= z - end - return tn end function LinearAlgebra.normalize(tn::AbstractTTN) - return normalize!(copy(tn)) + return normalize!(copy(tn)) end function _apply_to_ortho_region!(f, tn::AbstractTTN, x) - v = first(ortho_region(tn)) - tn[v] = f(tn[v], x) - return tn + v = first(ortho_region(tn)) + tn[v] = f(tn[v], x) + return tn end function _apply_to_ortho_region(f, tn::AbstractTTN, x) - return _apply_to_ortho_region!(f, copy(tn), x) + return _apply_to_ortho_region!(f, copy(tn), x) end Base.:*(tn::AbstractTTN, α::Number) = _apply_to_ortho_region(*, tn, α) @@ -159,112 +159,112 @@ Base.:/(tn::AbstractTTN, α::Number) = _apply_to_ortho_region(/, tn, α) Base.:-(tn::AbstractTTN) = -1 * tn function LinearAlgebra.rmul!(tn::AbstractTTN, α::Number) - return _apply_to_ortho_region!(*, tn, α) + return _apply_to_ortho_region!(*, tn, α) end function lognorm(tn::AbstractTTN) - if isone(length(ortho_region(tn))) - return log(norm(tn[only(ortho_region(tn))])) - end - lognorm2_tn = loginner(tn, tn) - rtol = eps(real(scalartype(tn))) * 10 - atol = rtol - if !IsApprox.isreal(lognorm2_tn, Approx(; rtol=rtol, atol=atol)) - @warn "log(norm²) is $lognorm2_T, which is not real up to a relative tolerance of $rtol and an absolute tolerance of $atol. Taking the real part, which may not be accurate." - end - return 0.5 * real(lognorm2_tn) + if isone(length(ortho_region(tn))) + return log(norm(tn[only(ortho_region(tn))])) + end + lognorm2_tn = loginner(tn, tn) + rtol = eps(real(scalartype(tn))) * 10 + atol = rtol + if !IsApprox.isreal(lognorm2_tn, Approx(; rtol = rtol, atol = atol)) + @warn "log(norm²) is $lognorm2_T, which is not real up to a relative tolerance of $rtol and an absolute tolerance of $atol. Taking the real part, which may not be accurate." + end + return 0.5 * real(lognorm2_tn) end function logdot(tn1::AbstractTTN, tn2::AbstractTTN; kwargs...) - return loginner(tn1, tn2; kwargs...) + return loginner(tn1, tn2; kwargs...) end # TODO: stick with this traversal or find optimal contraction sequence? function loginner( - tn1::AbstractTTN, tn2::AbstractTTN; root_vertex=GraphsExtensions.default_root_vertex(tn1) -) - N = nv(tn1) - if nv(tn2) != N - throw(DimensionMismatch("inner: mismatched number of vertices $N and $(nv(tn2))")) - end - tn1dag = sim(dag(tn1); sites=[]) - traversal_order = reverse(post_order_dfs_vertices(tn1, root_vertex)) - - O = tn1dag[root_vertex] * tn2[root_vertex] - - normO = norm(O) - log_inner_tot = log(normO) - O ./= normO - - for v in traversal_order[2:end] - O = (O * tn1dag[v]) * tn2[v] + tn1::AbstractTTN, tn2::AbstractTTN; root_vertex = GraphsExtensions.default_root_vertex(tn1) + ) + N = nv(tn1) + if nv(tn2) != N + throw(DimensionMismatch("inner: mismatched number of vertices $N and $(nv(tn2))")) + end + tn1dag = sim(dag(tn1); sites = []) + traversal_order = reverse(post_order_dfs_vertices(tn1, root_vertex)) + + O = tn1dag[root_vertex] * tn2[root_vertex] + normO = norm(O) - log_inner_tot += log(normO) + log_inner_tot = log(normO) O ./= normO - end - if !isreal(O[]) || real(O[]) < 0 - log_inner_tot += log(complex(O[])) - end - return log_inner_tot + for v in traversal_order[2:end] + O = (O * tn1dag[v]) * tn2[v] + normO = norm(O) + log_inner_tot += log(normO) + O ./= normO + end + + if !isreal(O[]) || real(O[]) < 0 + log_inner_tot += log(complex(O[])) + end + return log_inner_tot end function _add_maxlinkdims(tns::AbstractTTN...) - maxdims = Dictionary{edgetype(tns[1]),Int}() - for e in edges(tns[1]) - maxdims[e] = sum(tn -> linkdim(tn, e), tns) - maxdims[reverse(e)] = maxdims[e] - end - return maxdims + maxdims = Dictionary{edgetype(tns[1]), Int}() + for e in edges(tns[1]) + maxdims[e] = sum(tn -> linkdim(tn, e), tns) + maxdims[reverse(e)] = maxdims[e] + end + return maxdims end # TODO: actually implement this? function Base.:+( - ::Algorithm"densitymatrix", - tns::AbstractTTN...; - cutoff=1e-15, - root_vertex=GraphsExtensions.default_root_vertex(first(tns)), - kwargs..., -) - return error("Not implemented (yet) for trees.") + ::Algorithm"densitymatrix", + tns::AbstractTTN...; + cutoff = 1.0e-15, + root_vertex = GraphsExtensions.default_root_vertex(first(tns)), + kwargs..., + ) + return error("Not implemented (yet) for trees.") end function Base.:+( - ::Algorithm"directsum", - tns::AbstractTTN...; - root_vertex=GraphsExtensions.default_root_vertex(first(tns)), -) - @assert all(tn -> nv(first(tns)) == nv(tn), tns) - - # Output state - tn = ttn(siteinds(tns[1])) - - vs = post_order_dfs_vertices(tn, root_vertex) - es = post_order_dfs_edges(tn, root_vertex) - link_space = Dict{edgetype(tn),Index}() - - for v in reverse(vs) - edges = filter(e -> dst(e) == v || src(e) == v, es) - dims_in = findall(e -> dst(e) == v, edges) - dim_out = findfirst(e -> src(e) == v, edges) - ls = [Tuple(only(linkinds(tn, e)) for e in edges) for tn in tns] - tnv, lv = directsum( - (tns[i][v] => ls[i] for i in 1:length(tns))...; tags=tags.(first(ls)) + ::Algorithm"directsum", + tns::AbstractTTN...; + root_vertex = GraphsExtensions.default_root_vertex(first(tns)), ) - for din in dims_in - link_space[edges[din]] = lv[din] - end - if !isnothing(dim_out) - tnv = replaceind(tnv, lv[dim_out] => dag(link_space[edges[dim_out]])) + @assert all(tn -> nv(first(tns)) == nv(tn), tns) + + # Output state + tn = ttn(siteinds(tns[1])) + + vs = post_order_dfs_vertices(tn, root_vertex) + es = post_order_dfs_edges(tn, root_vertex) + link_space = Dict{edgetype(tn), Index}() + + for v in reverse(vs) + edges = filter(e -> dst(e) == v || src(e) == v, es) + dims_in = findall(e -> dst(e) == v, edges) + dim_out = findfirst(e -> src(e) == v, edges) + ls = [Tuple(only(linkinds(tn, e)) for e in edges) for tn in tns] + tnv, lv = directsum( + (tns[i][v] => ls[i] for i in 1:length(tns))...; tags = tags.(first(ls)) + ) + for din in dims_in + link_space[edges[din]] = lv[din] + end + if !isnothing(dim_out) + tnv = replaceind(tnv, lv[dim_out] => dag(link_space[edges[dim_out]])) + end + tn[v] = tnv end - tn[v] = tnv - end - return tn + return tn end # TODO: switch default algorithm once more are implemented -function Base.:+(tns::AbstractTTN...; alg=Algorithm"directsum"(), kwargs...) - return +(Algorithm(alg), tns...; kwargs...) +function Base.:+(tns::AbstractTTN...; alg = Algorithm"directsum"(), kwargs...) + return +(Algorithm(alg), tns...; kwargs...) end Base.:+(tn::AbstractTTN) = tn @@ -272,25 +272,25 @@ Base.:+(tn::AbstractTTN) = tn add(tns::AbstractTTN...; kwargs...) = +(tns...; kwargs...) function Base.:-(tn1::AbstractTTN, tn2::AbstractTTN; kwargs...) - return +(tn1, -tn2; kwargs...) + return +(tn1, -tn2; kwargs...) end function add(tn1::AbstractTTN, tn2::AbstractTTN; kwargs...) - return +(tn1, tn2; kwargs...) + return +(tn1, tn2; kwargs...) end function Base.isapprox( - x::AbstractTTN, - y::AbstractTTN; - atol::Real=0, - rtol::Real=Base.rtoldefault(scalartype(x), scalartype(y), atol), -) - d = norm(x - y) - if isfinite(d) - return d <= max(atol, rtol * max(norm(x), norm(y))) - else - error("In `isapprox(x::AbstractTTN, y::AbstractTTN)`, `norm(x - y)` is not finite") - end + x::AbstractTTN, + y::AbstractTTN; + atol::Real = 0, + rtol::Real = Base.rtoldefault(scalartype(x), scalartype(y), atol), + ) + d = norm(x - y) + if isfinite(d) + return d <= max(atol, rtol * max(norm(x), norm(y))) + else + error("In `isapprox(x::AbstractTTN, y::AbstractTTN)`, `norm(x - y)` is not finite") + end end # @@ -299,71 +299,71 @@ end # TODO: implement using multi-graph disjoint union function ITensors.inner( - y::AbstractTTN, - A::AbstractTTN, - x::AbstractTTN; - root_vertex=GraphsExtensions.default_root_vertex(x), -) - traversal_order = reverse(post_order_dfs_vertices(x, root_vertex)) - ydag = sim(dag(y); sites=[]) - x = sim(x; sites=[]) - O = ydag[root_vertex] * A[root_vertex] * x[root_vertex] - for v in traversal_order[2:end] - O = O * ydag[v] * A[v] * x[v] - end - return O[] + y::AbstractTTN, + A::AbstractTTN, + x::AbstractTTN; + root_vertex = GraphsExtensions.default_root_vertex(x), + ) + traversal_order = reverse(post_order_dfs_vertices(x, root_vertex)) + ydag = sim(dag(y); sites = []) + x = sim(x; sites = []) + O = ydag[root_vertex] * A[root_vertex] * x[root_vertex] + for v in traversal_order[2:end] + O = O * ydag[v] * A[v] * x[v] + end + return O[] end # TODO: implement using multi-graph disjoint function ITensors.inner( - B::AbstractTTN, - y::AbstractTTN, - A::AbstractTTN, - x::AbstractTTN; - root_vertex=GraphsExtensions.default_root_vertex(B), -) - N = nv(B) - if nv(y) != N || nv(x) != N || nv(A) != N - throw( - DimensionMismatch( - "inner: mismatched number of vertices $N and $(nv(x)) or $(nv(y)) or $(nv(A))" - ), + B::AbstractTTN, + y::AbstractTTN, + A::AbstractTTN, + x::AbstractTTN; + root_vertex = GraphsExtensions.default_root_vertex(B), ) - end - ydag = sim(linkinds, dag(y)) - Bdag = sim(linkinds, dag(B)) - traversal_order = reverse(post_order_dfs_vertices(x, root_vertex)) - yB = ydag[root_vertex] * Bdag[root_vertex] - Ax = A[root_vertex] * x[root_vertex] - O = yB * Ax - for v in traversal_order[2:end] - yB = ydag[v] * Bdag[v] - Ax = A[v] * x[v] - yB *= O + N = nv(B) + if nv(y) != N || nv(x) != N || nv(A) != N + throw( + DimensionMismatch( + "inner: mismatched number of vertices $N and $(nv(x)) or $(nv(y)) or $(nv(A))" + ), + ) + end + ydag = sim(linkinds, dag(y)) + Bdag = sim(linkinds, dag(B)) + traversal_order = reverse(post_order_dfs_vertices(x, root_vertex)) + yB = ydag[root_vertex] * Bdag[root_vertex] + Ax = A[root_vertex] * x[root_vertex] O = yB * Ax - end - return O[] + for v in traversal_order[2:end] + yB = ydag[v] * Bdag[v] + Ax = A[v] * x[v] + yB *= O + O = yB * Ax + end + return O[] end function expect( - operator::String, - state::AbstractTTN; - vertices=vertices(state), - # TODO: verify that this is a sane default - root_vertex=GraphsExtensions.default_root_vertex(state), -) - # TODO: Optimize this with proper caching. - state /= norm(state) - sites = siteinds(state) - ordered_vertices = reverse(post_order_dfs_vertices(sites, root_vertex)) - res = Dictionary(vertices, undef) - for v in ordered_vertices - !(v in vertices) && continue - state = orthogonalize(state, v) - @assert isone(length(sites[v])) - #ToDo: Add compatibility with more than a single index per vertex - op_v = op(operator, only(sites[v])) - res[v] = (dag(state[v]) * apply(op_v, state[v]))[] - end - return mapreduce(typeof, promote_type, res).(res) + operator::String, + state::AbstractTTN; + vertices = vertices(state), + # TODO: verify that this is a sane default + root_vertex = GraphsExtensions.default_root_vertex(state), + ) + # TODO: Optimize this with proper caching. + state /= norm(state) + sites = siteinds(state) + ordered_vertices = reverse(post_order_dfs_vertices(sites, root_vertex)) + res = Dictionary(vertices, undef) + for v in ordered_vertices + !(v in vertices) && continue + state = orthogonalize(state, v) + @assert isone(length(sites[v])) + #ToDo: Add compatibility with more than a single index per vertex + op_v = op(operator, only(sites[v])) + res[v] = (dag(state[v]) * apply(op_v, state[v]))[] + end + return mapreduce(typeof, promote_type, res).(res) end diff --git a/src/treetensornetworks/opsum_to_ttn/matelem.jl b/src/treetensornetworks/opsum_to_ttn/matelem.jl index d1b98dc0..c81d201e 100644 --- a/src/treetensornetworks/opsum_to_ttn/matelem.jl +++ b/src/treetensornetworks/opsum_to_ttn/matelem.jl @@ -1,12 +1,11 @@ - # # MatElem # struct MatElem{T} - row::Int - col::Int - val::T + row::Int + col::Int + val::T end #function Base.show(io::IO,m::MatElem) @@ -14,28 +13,28 @@ end #end function toMatrix(els::Vector{MatElem{T}})::Matrix{T} where {T} - nr = 0 - nc = 0 - for el in els - nr = max(nr, el.row) - nc = max(nc, el.col) - end - M = zeros(T, nr, nc) - for el in els - M[el.row, el.col] = el.val - end - return M + nr = 0 + nc = 0 + for el in els + nr = max(nr, el.row) + nc = max(nc, el.col) + end + M = zeros(T, nr, nc) + for el in els + M[el.row, el.col] = el.val + end + return M end function Base.:(==)(m1::MatElem{T}, m2::MatElem{T})::Bool where {T} - return (m1.row == m2.row && m1.col == m2.col && m1.val == m2.val) + return (m1.row == m2.row && m1.col == m2.col && m1.val == m2.val) end function Base.isless(m1::MatElem{T}, m2::MatElem{T})::Bool where {T} - if m1.row != m2.row - return m1.row < m2.row - elseif m1.col != m2.col - return m1.col < m2.col - end - return m1.val < m2.val + if m1.row != m2.row + return m1.row < m2.row + elseif m1.col != m2.col + return m1.col < m2.col + end + return m1.val < m2.val end diff --git a/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl b/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl index 796a1aa5..811b49cf 100644 --- a/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl +++ b/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl @@ -6,37 +6,37 @@ using ITensors.LazyApply: Prod, Sum, coefficient using ITensors.NDTensors: Block, blockdim, maxdim, nblocks, nnzblocks, truncate! using ITensors.Ops: argument, coefficient, Op, OpSum, name, params, site, terms, which_op using NamedGraphs.GraphsExtensions: - GraphsExtensions, boundary_edges, degrees, is_leaf_vertex, vertex_path + GraphsExtensions, boundary_edges, degrees, is_leaf_vertex, vertex_path using StaticArrays: MVector -# +# # Utility methods -# +# function align_edges(edges, reference_edges) - return intersect(Iterators.flatten(zip(edges, reverse.(edges))), reference_edges) + return intersect(Iterators.flatten(zip(edges, reverse.(edges))), reference_edges) end function align_and_reorder_edges(edges, reference_edges) - return intersect(reference_edges, align_edges(edges, reference_edges)) + return intersect(reference_edges, align_edges(edges, reference_edges)) end function split_at_vertex(g::AbstractGraph, v) - g = copy(g) - rem_vertex!(g, v) - return Set.(connected_components(g)) + g = copy(g) + rem_vertex!(g, v) + return Set.(connected_components(g)) end -# +# # Tree adaptations of functionalities in ITensors.jl/src/physics/autompo/opsum_to_mpo.jl -# +# function determine_coefficient_type(terms) - isempty(terms) && return Float64 - if all(t -> isreal(coefficient(t)), terms) - return real(typeof(coefficient(first(terms)))) - end - return typeof(coefficient(first(terms))) + isempty(terms) && return Float64 + if all(t -> isreal(coefficient(t)), terms) + return real(typeof(coefficient(first(terms)))) + end + return typeof(coefficient(first(terms))) end """ @@ -46,395 +46,397 @@ Construct a TreeTensorNetwork from a symbolic OpSum representation of a Hamiltonian, compressing shared interaction channels. """ function ttn_svd(os::OpSum, sites::IndsNetwork, root_vertex; kwargs...) - # Function barrier to improve type stability - coefficient_type = determine_coefficient_type(terms(os)) - return ttn_svd(coefficient_type, os, sites, root_vertex; kwargs...) + # Function barrier to improve type stability + coefficient_type = determine_coefficient_type(terms(os)) + return ttn_svd(coefficient_type, os, sites, root_vertex; kwargs...) end # TODO: should be equivalent to `sort!(v); unique!(v)` however, -# Base.unique! is giving incorrect results for data involving the +# Base.unique! is giving incorrect results for data involving the # Prod type from LazyApply. This could be a combination of Base.unique! # not strictly relying on isequal combined with an incorrect # implementation of isequal/isless for Prod. function _sort_unique!(v::Vector) - N = length(v) - (N == 0) && return nothing - sort!(v) - n = 1 - u = 2 - while u <= N - while u < N && v[u] == v[n] - u += 1 - end - if v[u] != v[n] - v[n + 1] = v[u] - n += 1 + N = length(v) + (N == 0) && return nothing + sort!(v) + n = 1 + u = 2 + while u <= N + while u < N && v[u] == v[n] + u += 1 + end + if v[u] != v[n] + v[n + 1] = v[u] + n += 1 + end + u += 1 end - u += 1 - end - resize!(v, n) - return nothing + resize!(v, n) + return nothing end function pos_in_link!(linkmap::Dict, k) - isempty(k) && return -1 - pos = get(linkmap, k, -1) - if pos == -1 - pos = length(linkmap) + 1 - linkmap[k] = pos - end - return pos + isempty(k) && return -1 + pos = get(linkmap, k, -1) + if pos == -1 + pos = length(linkmap) + 1 + linkmap[k] = pos + end + return pos end function make_symbolic_ttn( - coefficient_type, - opsum::OpSum, - sites::IndsNetwork; - ordered_verts, - ordered_edges, - root_vertex, - term_qn_map, -) - inmaps = Dict{Pair{edgetype(sites),QN},Dict{Vector{Op},Int}}() - outmaps = Dict{Pair{edgetype(sites),QN},Dict{Vector{Op},Int}}() - - #g = underlying_graph(sites) - - # Bond coefficients for incoming edge channels. - # These become the "M" coefficient matrices that get SVD'd. - inbond_coefs = Dict( - e => Dict{QN,Vector{MatElem{coefficient_type}}}() for e in ordered_edges - ) - - # List of terms for which the coefficient has been added to a site factor - site_coef_done = Prod{Op}[] - - # Temporary symbolic representation of TTN Hamiltonian - symbolic_ttn = Dict( - v => QNArrElem{Scaled{coefficient_type,Prod{Op}},degree(sites, v)}[] for - v in ordered_verts - ) - - # Build compressed finite state machine representation (symbolic_ttn) - for v in ordered_verts - v_degree = degree(sites, v) - # For every vertex, find all edges that contain this vertex - # (align_and_reorder_edges makes the output of indicident edges match the - # direction and ordering match that of ordered_edges) - edges = align_and_reorder_edges(incident_edges(sites, v), ordered_edges) - - # Use the corresponding ordering as index order for tensor elements at this site - dim_in = findfirst(e -> dst(e) == v, edges) - edge_in = (isnothing(dim_in) ? nothing : edges[dim_in]) - dims_out = findall(e -> src(e) == v, edges) - edges_out = edges[dims_out] - - # For every site v' except v, determine the incident edge to v - # that lies in the edge_path(v',v) - # TODO: better way to make which_incident_edge below? - subgraphs = split_at_vertex(sites, v) - _boundary_edges = [ - only(boundary_edges(underlying_graph(sites), subgraph)) for subgraph in subgraphs - ] - _boundary_edges = align_edges(_boundary_edges, edges) - which_incident_edge = Dict( - Iterators.flatten([ - subgraphs[i] .=> ((_boundary_edges[i]),) for i in eachindex(subgraphs) - ]), + coefficient_type, + opsum::OpSum, + sites::IndsNetwork; + ordered_verts, + ordered_edges, + root_vertex, + term_qn_map, + ) + inmaps = Dict{Pair{edgetype(sites), QN}, Dict{Vector{Op}, Int}}() + outmaps = Dict{Pair{edgetype(sites), QN}, Dict{Vector{Op}, Int}}() + + #g = underlying_graph(sites) + + # Bond coefficients for incoming edge channels. + # These become the "M" coefficient matrices that get SVD'd. + inbond_coefs = Dict( + e => Dict{QN, Vector{MatElem{coefficient_type}}}() for e in ordered_edges + ) + + # List of terms for which the coefficient has been added to a site factor + site_coef_done = Prod{Op}[] + + # Temporary symbolic representation of TTN Hamiltonian + symbolic_ttn = Dict( + v => QNArrElem{Scaled{coefficient_type, Prod{Op}}, degree(sites, v)}[] for + v in ordered_verts ) - # Sanity check, leaves only have single incoming or outgoing edge - @assert !isempty(dims_out) || !isnothing(dim_in) - (isempty(dims_out) || isnothing(dim_in)) && @assert is_leaf_vertex(sites, v) - - for term in opsum - # Loop over OpSum and pick out terms that act on current vertex - ops = ITensors.terms(term) - if v in ITensors.site.(ops) - crosses_vertex = true - else - crosses_vertex = - !isone(length(Set([which_incident_edge[site] for site in site.(ops)]))) - end - # If term doesn't cross vertex, skip it - crosses_vertex || continue - - # Filter out ops that acts on current vertex - onsite_ops = filter(t -> (site(t) == v), ops) - non_onsite_ops = setdiff(ops, onsite_ops) - - # Filter out ops that come in from the direction of the incoming edge - incoming_ops = filter(t -> which_incident_edge[site(t)] == edge_in, non_onsite_ops) - - # Also store all non-incoming ops in standard order, used for channel merging - non_incoming_ops = filter( - t -> (site(t) == v) || which_incident_edge[site(t)] != edge_in, ops - ) - - # For every outgoing edge, filter out ops that go out along that edge - outgoing_ops = Dict( - e => filter(t -> which_incident_edge[site(t)] == e, non_onsite_ops) for - e in edges_out - ) - - # Compute QNs - incoming_qn = term_qn_map(incoming_ops) - non_incoming_qn = term_qn_map(non_incoming_ops) - site_qn = term_qn_map(onsite_ops) - - # Initialize QNArrayElement indices and quantum numbers - T_inds = MVector{v_degree}(fill(-1, v_degree)) - T_qns = MVector{v_degree}(fill(QN(), v_degree)) - # initialize ArrayElement indices for inbond_coefs - bond_row = -1 - bond_col = -1 - if !isempty(incoming_ops) - # Get the correct map from edge=>QN to term and channel. - # This checks if term exists on edge=>QN (otherwise insert it) and returns its index. - coutmap = get!(outmaps, edge_in => non_incoming_qn, Dict{Vector{Op},Int}()) - cinmap = get!(inmaps, edge_in => -incoming_qn, Dict{Vector{Op},Int}()) - - bond_row = pos_in_link!(cinmap, incoming_ops) - bond_col = pos_in_link!(coutmap, non_incoming_ops) # get incoming channel - bond_coef = convert(coefficient_type, coefficient(term)) - q_inbond_coefs = get!( - inbond_coefs[edge_in], incoming_qn, MatElem{coefficient_type}[] + # Build compressed finite state machine representation (symbolic_ttn) + for v in ordered_verts + v_degree = degree(sites, v) + # For every vertex, find all edges that contain this vertex + # (align_and_reorder_edges makes the output of indicident edges match the + # direction and ordering match that of ordered_edges) + edges = align_and_reorder_edges(incident_edges(sites, v), ordered_edges) + + # Use the corresponding ordering as index order for tensor elements at this site + dim_in = findfirst(e -> dst(e) == v, edges) + edge_in = (isnothing(dim_in) ? nothing : edges[dim_in]) + dims_out = findall(e -> src(e) == v, edges) + edges_out = edges[dims_out] + + # For every site v' except v, determine the incident edge to v + # that lies in the edge_path(v',v) + # TODO: better way to make which_incident_edge below? + subgraphs = split_at_vertex(sites, v) + _boundary_edges = [ + only(boundary_edges(underlying_graph(sites), subgraph)) for subgraph in subgraphs + ] + _boundary_edges = align_edges(_boundary_edges, edges) + which_incident_edge = Dict( + Iterators.flatten( + [ + subgraphs[i] .=> ((_boundary_edges[i]),) for i in eachindex(subgraphs) + ] + ), ) - push!(q_inbond_coefs, MatElem(bond_row, bond_col, bond_coef)) - T_inds[dim_in] = bond_col - T_qns[dim_in] = -incoming_qn - end - for dout in dims_out - out_edge = edges[dout] - out_op = outgoing_ops[out_edge] - coutmap = get!(outmaps, out_edge => term_qn_map(out_op), Dict{Vector{Op},Int}()) - # Add outgoing channel - T_inds[dout] = pos_in_link!(coutmap, out_op) - T_qns[dout] = term_qn_map(out_op) - end - # If term starts at this site, add its coefficient as a site factor - site_coef = one(coefficient_type) - if (isnothing(dim_in) || T_inds[dim_in] == -1) && argument(term) ∉ site_coef_done - site_coef = convert(coefficient_type, coefficient(term)) - push!(site_coef_done, argument(term)) - end - # Add onsite identity for interactions passing through vertex - if isempty(onsite_ops) - if !ITensors.using_auto_fermion() && isfermionic(incoming_ops, sites) - push!(onsite_ops, Op("F", v)) - else - push!(onsite_ops, Op("Id", v)) + + # Sanity check, leaves only have single incoming or outgoing edge + @assert !isempty(dims_out) || !isnothing(dim_in) + (isempty(dims_out) || isnothing(dim_in)) && @assert is_leaf_vertex(sites, v) + + for term in opsum + # Loop over OpSum and pick out terms that act on current vertex + ops = ITensors.terms(term) + if v in ITensors.site.(ops) + crosses_vertex = true + else + crosses_vertex = + !isone(length(Set([which_incident_edge[site] for site in site.(ops)]))) + end + # If term doesn't cross vertex, skip it + crosses_vertex || continue + + # Filter out ops that acts on current vertex + onsite_ops = filter(t -> (site(t) == v), ops) + non_onsite_ops = setdiff(ops, onsite_ops) + + # Filter out ops that come in from the direction of the incoming edge + incoming_ops = filter(t -> which_incident_edge[site(t)] == edge_in, non_onsite_ops) + + # Also store all non-incoming ops in standard order, used for channel merging + non_incoming_ops = filter( + t -> (site(t) == v) || which_incident_edge[site(t)] != edge_in, ops + ) + + # For every outgoing edge, filter out ops that go out along that edge + outgoing_ops = Dict( + e => filter(t -> which_incident_edge[site(t)] == e, non_onsite_ops) for + e in edges_out + ) + + # Compute QNs + incoming_qn = term_qn_map(incoming_ops) + non_incoming_qn = term_qn_map(non_incoming_ops) + site_qn = term_qn_map(onsite_ops) + + # Initialize QNArrayElement indices and quantum numbers + T_inds = MVector{v_degree}(fill(-1, v_degree)) + T_qns = MVector{v_degree}(fill(QN(), v_degree)) + # initialize ArrayElement indices for inbond_coefs + bond_row = -1 + bond_col = -1 + if !isempty(incoming_ops) + # Get the correct map from edge=>QN to term and channel. + # This checks if term exists on edge=>QN (otherwise insert it) and returns its index. + coutmap = get!(outmaps, edge_in => non_incoming_qn, Dict{Vector{Op}, Int}()) + cinmap = get!(inmaps, edge_in => -incoming_qn, Dict{Vector{Op}, Int}()) + + bond_row = pos_in_link!(cinmap, incoming_ops) + bond_col = pos_in_link!(coutmap, non_incoming_ops) # get incoming channel + bond_coef = convert(coefficient_type, coefficient(term)) + q_inbond_coefs = get!( + inbond_coefs[edge_in], incoming_qn, MatElem{coefficient_type}[] + ) + push!(q_inbond_coefs, MatElem(bond_row, bond_col, bond_coef)) + T_inds[dim_in] = bond_col + T_qns[dim_in] = -incoming_qn + end + for dout in dims_out + out_edge = edges[dout] + out_op = outgoing_ops[out_edge] + coutmap = get!(outmaps, out_edge => term_qn_map(out_op), Dict{Vector{Op}, Int}()) + # Add outgoing channel + T_inds[dout] = pos_in_link!(coutmap, out_op) + T_qns[dout] = term_qn_map(out_op) + end + # If term starts at this site, add its coefficient as a site factor + site_coef = one(coefficient_type) + if (isnothing(dim_in) || T_inds[dim_in] == -1) && argument(term) ∉ site_coef_done + site_coef = convert(coefficient_type, coefficient(term)) + push!(site_coef_done, argument(term)) + end + # Add onsite identity for interactions passing through vertex + if isempty(onsite_ops) + if !ITensors.using_auto_fermion() && isfermionic(incoming_ops, sites) + push!(onsite_ops, Op("F", v)) + else + push!(onsite_ops, Op("Id", v)) + end + end + # Save indices and value of symbolic tensor entry + el = QNArrElem(T_qns, T_inds, site_coef * Prod(onsite_ops)) + push!(symbolic_ttn[v], el) end - end - # Save indices and value of symbolic tensor entry - el = QNArrElem(T_qns, T_inds, site_coef * Prod(onsite_ops)) - push!(symbolic_ttn[v], el) + _sort_unique!(symbolic_ttn[v]) end - _sort_unique!(symbolic_ttn[v]) - end - return symbolic_ttn, inbond_coefs + return symbolic_ttn, inbond_coefs end function svd_bond_coefs( - coefficient_type, sites, inbond_coefs; ordered_verts, ordered_edges, kws... -) - Vs = Dict(e => Dict{QN,Matrix{coefficient_type}}() for e in ordered_edges) - for v in ordered_verts - edges = align_and_reorder_edges(incident_edges(sites, v), ordered_edges) - dim_in = findfirst(e -> dst(e) == v, edges) - if !isnothing(dim_in) && !isempty(inbond_coefs[edges[dim_in]]) - for (q, mat) in inbond_coefs[edges[dim_in]] - M = toMatrix(mat) - U, S, V = svd(M) - P = S .^ 2 - truncate!(P; kws...) - tdim = length(P) - nc = size(M, 2) - Vs[edges[dim_in]][q] = Matrix{coefficient_type}(V[1:nc, 1:tdim]) - end + coefficient_type, sites, inbond_coefs; ordered_verts, ordered_edges, kws... + ) + Vs = Dict(e => Dict{QN, Matrix{coefficient_type}}() for e in ordered_edges) + for v in ordered_verts + edges = align_and_reorder_edges(incident_edges(sites, v), ordered_edges) + dim_in = findfirst(e -> dst(e) == v, edges) + if !isnothing(dim_in) && !isempty(inbond_coefs[edges[dim_in]]) + for (q, mat) in inbond_coefs[edges[dim_in]] + M = toMatrix(mat) + U, S, V = svd(M) + P = S .^ 2 + truncate!(P; kws...) + tdim = length(P) + nc = size(M, 2) + Vs[edges[dim_in]][q] = Matrix{coefficient_type}(V[1:nc, 1:tdim]) + end + end end - end - return Vs + return Vs end function compress_ttn( - coefficient_type, sites0, Hflux, symbolic_ttn, Vs; ordered_verts, ordered_edges -) - # Insert dummy indices on internal vertices, these will not show up in the final tensor - # TODO: come up with a better solution for this - sites = copy(sites0) - is_internal = Dict{vertextype(sites),Bool}() - for v in ordered_verts - is_internal[v] = isempty(sites[v]) - if isempty(sites[v]) - # FIXME: This logic only works for trivial flux, breaks for nonzero flux - # TODO: add assert or fix and add test! - sites[v] = [Index(Hflux => 1)] + coefficient_type, sites0, Hflux, symbolic_ttn, Vs; ordered_verts, ordered_edges + ) + # Insert dummy indices on internal vertices, these will not show up in the final tensor + # TODO: come up with a better solution for this + sites = copy(sites0) + is_internal = Dict{vertextype(sites), Bool}() + for v in ordered_verts + is_internal[v] = isempty(sites[v]) + if isempty(sites[v]) + # FIXME: This logic only works for trivial flux, breaks for nonzero flux + # TODO: add assert or fix and add test! + sites[v] = [Index(Hflux => 1)] + end end - end - linkdir_ref = ITensors.In # safe to always use autofermion default here - # Compress this symbolic_ttn representation into dense form - thishasqns = any(v -> hasqns(sites[v]), vertices(sites)) - - link_space = Dict{edgetype(sites),Index}() - for e in ordered_edges - operator_blocks = [q => size(Vq, 2) for (q, Vq) in Vs[e]] - link_space[e] = Index( - QN() => 1, operator_blocks..., Hflux => 1; tags=edge_tag(e), dir=linkdir_ref - ) - end + linkdir_ref = ITensors.In # safe to always use autofermion default here + # Compress this symbolic_ttn representation into dense form + thishasqns = any(v -> hasqns(sites[v]), vertices(sites)) - H = ttn(sites0) # initialize TTN without the dummy indices added - function qnblock(i::Index, q::QN) - for b in 2:(nblocks(i) - 1) - flux(i, Block(b)) == q && return b + link_space = Dict{edgetype(sites), Index}() + for e in ordered_edges + operator_blocks = [q => size(Vq, 2) for (q, Vq) in Vs[e]] + link_space[e] = Index( + QN() => 1, operator_blocks..., Hflux => 1; tags = edge_tag(e), dir = linkdir_ref + ) end - return error("Could not find block of QNIndex with matching QN") - end - qnblockdim(i::Index, q::QN) = blockdim(i, qnblock(i, q)) - - for v in ordered_verts - v_degree = degree(sites, v) - # Redo the whole thing like before - # TODO: use neighborhood instead of going through all edges, see above - edges = align_and_reorder_edges(incident_edges(sites, v), ordered_edges) - dim_in = findfirst(e -> dst(e) == v, edges) - dims_out = findall(e -> src(e) == v, edges) - # slice isometries at this vertex - Vv = [Vs[e] for e in edges] - linkinds = [link_space[e] for e in edges] - - # construct blocks - blocks = Dict{Tuple{Block{v_degree},Vector{Op}},Array{coefficient_type,v_degree}}() - for el in symbolic_ttn[v] - t = el.val - (abs(coefficient(t)) > eps(real(coefficient_type))) || continue - block_helper_inds = fill(-1, v_degree) # we manipulate T_inds later, and loose track of ending/starting information, so keep track of it here - T_inds = el.idxs - T_qns = el.qn_idxs - ct = convert(coefficient_type, coefficient(t)) - sublinkdims = [ - (T_inds[i] == -1 ? 1 : qnblockdim(linkinds[i], T_qns[i])) for i in 1:v_degree - ] - zero_arr() = zeros(coefficient_type, sublinkdims...) - terminal_dims = findall(d -> T_inds[d] == -1, 1:v_degree) # directions in which term starts or ends - normal_dims = findall(d -> T_inds[d] ≠ -1, 1:v_degree) # normal dimensions, do truncation thingies - T_inds[terminal_dims] .= 1 # start in channel 1 ###?? - block_helper_inds[terminal_dims] .= 1 - for dout in filter(d -> d ∈ terminal_dims, dims_out) - T_inds[dout] = sublinkdims[dout] # end in channel linkdims[d] for each dimension d - @assert isone(T_inds[dout]) - block_helper_inds[dout] = nblocks(linkinds[dout]) - end - - # set non-trivial helper inds - for d in normal_dims - block_helper_inds[d] = qnblock(linkinds[d], T_qns[d]) - end - @assert all(≠(-1), block_helper_inds)# check that all block indices are set - - # make and fill Block - theblock = Block(Tuple(block_helper_inds)) - if isempty(normal_dims) - M = get!(blocks, (theblock, terms(t)), zero_arr()) - @assert isone(length(M)) - M[] += ct - else - M = get!(blocks, (theblock, terms(t)), zero_arr()) - dim_ranges = Tuple(size(Vv[d][T_qns[d]], 2) for d in normal_dims) - for c in CartesianIndices(dim_ranges) # applies isometries in a element-wise manner - z = ct - temp_inds = copy(T_inds) - for (i, d) in enumerate(normal_dims) - V_factor = Vv[d][T_qns[d]][T_inds[d], c[i]] - z *= (d == dim_in ? conj(V_factor) : V_factor) # conjugate incoming isometry factor - temp_inds[d] = c[i] - end - M[temp_inds...] += z + + H = ttn(sites0) # initialize TTN without the dummy indices added + function qnblock(i::Index, q::QN) + for b in 2:(nblocks(i) - 1) + flux(i, Block(b)) == q && return b end - end + return error("Could not find block of QNIndex with matching QN") end + qnblockdim(i::Index, q::QN) = blockdim(i, qnblock(i, q)) + + for v in ordered_verts + v_degree = degree(sites, v) + # Redo the whole thing like before + # TODO: use neighborhood instead of going through all edges, see above + edges = align_and_reorder_edges(incident_edges(sites, v), ordered_edges) + dim_in = findfirst(e -> dst(e) == v, edges) + dims_out = findall(e -> src(e) == v, edges) + # slice isometries at this vertex + Vv = [Vs[e] for e in edges] + linkinds = [link_space[e] for e in edges] + + # construct blocks + blocks = Dict{Tuple{Block{v_degree}, Vector{Op}}, Array{coefficient_type, v_degree}}() + for el in symbolic_ttn[v] + t = el.val + (abs(coefficient(t)) > eps(real(coefficient_type))) || continue + block_helper_inds = fill(-1, v_degree) # we manipulate T_inds later, and loose track of ending/starting information, so keep track of it here + T_inds = el.idxs + T_qns = el.qn_idxs + ct = convert(coefficient_type, coefficient(t)) + sublinkdims = [ + (T_inds[i] == -1 ? 1 : qnblockdim(linkinds[i], T_qns[i])) for i in 1:v_degree + ] + zero_arr() = zeros(coefficient_type, sublinkdims...) + terminal_dims = findall(d -> T_inds[d] == -1, 1:v_degree) # directions in which term starts or ends + normal_dims = findall(d -> T_inds[d] ≠ -1, 1:v_degree) # normal dimensions, do truncation thingies + T_inds[terminal_dims] .= 1 # start in channel 1 ###?? + block_helper_inds[terminal_dims] .= 1 + for dout in filter(d -> d ∈ terminal_dims, dims_out) + T_inds[dout] = sublinkdims[dout] # end in channel linkdims[d] for each dimension d + @assert isone(T_inds[dout]) + block_helper_inds[dout] = nblocks(linkinds[dout]) + end + + # set non-trivial helper inds + for d in normal_dims + block_helper_inds[d] = qnblock(linkinds[d], T_qns[d]) + end + @assert all(≠(-1), block_helper_inds) # check that all block indices are set + + # make and fill Block + theblock = Block(Tuple(block_helper_inds)) + if isempty(normal_dims) + M = get!(blocks, (theblock, terms(t)), zero_arr()) + @assert isone(length(M)) + M[] += ct + else + M = get!(blocks, (theblock, terms(t)), zero_arr()) + dim_ranges = Tuple(size(Vv[d][T_qns[d]], 2) for d in normal_dims) + for c in CartesianIndices(dim_ranges) # applies isometries in a element-wise manner + z = ct + temp_inds = copy(T_inds) + for (i, d) in enumerate(normal_dims) + V_factor = Vv[d][T_qns[d]][T_inds[d], c[i]] + z *= (d == dim_in ? conj(V_factor) : V_factor) # conjugate incoming isometry factor + temp_inds[d] = c[i] + end + M[temp_inds...] += z + end + end + end - H[v] = ITensor() + H[v] = ITensor() - # Set the final arrow directions - if !isnothing(dim_in) - linkinds[dim_in] = dag(linkinds[dim_in]) - end + # Set the final arrow directions + if !isnothing(dim_in) + linkinds[dim_in] = dag(linkinds[dim_in]) + end - for ((b, q_op), m) in blocks - Op = computeSiteProd(sites, Prod(q_op)) - if hasqns(Op) - # FIXME: this may not be safe, we may want to check for the equivalent (zero tensor?) case in the dense case as well - iszero(nnzblocks(Op)) && continue - end - sq = flux(Op) - if !isnothing(sq) - rq = (b[1] == 1 ? Hflux : first(space(linkinds[1])[b[1]])) # get row (dim_in) QN - cq = rq - sq # get column (out_dims) QN - if ITensors.using_auto_fermion() - # we need to account for the direct product below ordering the physical indices as the last indices - # although they are in between incoming and outgoing indices in the canonical site-ordering - perm = (1, 3, 2) - if ITensors.compute_permfactor(perm, rq, sq, cq) == -1 - Op .*= -1 - end + for ((b, q_op), m) in blocks + Op = computeSiteProd(sites, Prod(q_op)) + if hasqns(Op) + # FIXME: this may not be safe, we may want to check for the equivalent (zero tensor?) case in the dense case as well + iszero(nnzblocks(Op)) && continue + end + sq = flux(Op) + if !isnothing(sq) + rq = (b[1] == 1 ? Hflux : first(space(linkinds[1])[b[1]])) # get row (dim_in) QN + cq = rq - sq # get column (out_dims) QN + if ITensors.using_auto_fermion() + # we need to account for the direct product below ordering the physical indices as the last indices + # although they are in between incoming and outgoing indices in the canonical site-ordering + perm = (1, 3, 2) + if ITensors.compute_permfactor(perm, rq, sq, cq) == -1 + Op .*= -1 + end + end + end + T = ITensors.BlockSparseTensor(coefficient_type, [b], linkinds) + T[b] .= m + iT = itensor(T) + if !thishasqns + iT = removeqns(iT) + end + + if is_internal[v] + H[v] += iT + else + #TODO: Remove this assert since it seems to be costly + #if hasqns(iT) + # @assert flux(iT * Op) == Hflux + #end + H[v] += (iT * Op) + end end - end - T = ITensors.BlockSparseTensor(coefficient_type, [b], linkinds) - T[b] .= m - iT = itensor(T) - if !thishasqns - iT = removeqns(iT) - end - - if is_internal[v] - H[v] += iT - else - #TODO: Remove this assert since it seems to be costly - #if hasqns(iT) - # @assert flux(iT * Op) == Hflux - #end - H[v] += (iT * Op) - end - end - linkdims = dim.(linkinds) - # add starting and ending identity operators - idT = zeros(coefficient_type, linkdims...) - if isnothing(dim_in) - # only one real starting identity - idT[ones(Int, v_degree)...] = 1.0 - end - # ending identities are a little more involved - if !isnothing(dim_in) - # place identity if all channels end - idT[linkdims...] = 1.0 - # place identity from start of incoming channel to start of each single outgoing channel, and end all other channels - idT_end_inds = [linkdims...] - #this should really be an int - idT_end_inds[dim_in] = 1 - for dout in dims_out - idT_end_inds[dout] = 1 - idT[idT_end_inds...] = 1.0 - # reset - idT_end_inds[dout] = linkdims[dout] - end - end + linkdims = dim.(linkinds) + # add starting and ending identity operators + idT = zeros(coefficient_type, linkdims...) + if isnothing(dim_in) + # only one real starting identity + idT[ones(Int, v_degree)...] = 1.0 + end + # ending identities are a little more involved + if !isnothing(dim_in) + # place identity if all channels end + idT[linkdims...] = 1.0 + # place identity from start of incoming channel to start of each single outgoing channel, and end all other channels + idT_end_inds = [linkdims...] + #this should really be an int + idT_end_inds[dim_in] = 1 + for dout in dims_out + idT_end_inds[dout] = 1 + idT[idT_end_inds...] = 1.0 + # reset + idT_end_inds[dout] = linkdims[dout] + end + end - T = itensor(idT, linkinds) - if !thishasqns - T = removeqns(T) - end - if is_internal[v] - H[v] += T - else - H[v] += T * ITensorNetworks.computeSiteProd(sites, Prod([(Op("Id", v))])) + T = itensor(idT, linkinds) + if !thishasqns + T = removeqns(T) + end + if is_internal[v] + H[v] += T + else + H[v] += T * ITensorNetworks.computeSiteProd(sites, Prod([(Op("Id", v))])) + end end - end - return H + return H end # @@ -444,184 +446,185 @@ end # (Previously it was the function calc_qn.) # struct TermQNMap{V} - sites::IndsNetwork - op_cache::Dict{Pair{String,V},ITensor} - TermQNMap{V}(s) where {V} = new{V}(s, Dict{Pair{String,V},ITensor}()) + sites::IndsNetwork + op_cache::Dict{Pair{String, V}, ITensor} + TermQNMap{V}(s) where {V} = new{V}(s, Dict{Pair{String, V}, ITensor}()) end function (t::TermQNMap)(term) - q = QN() - for st in term - op_tensor = get(t.op_cache, which_op(st) => site(st), nothing) - if op_tensor === nothing - op_tensor = op(t.sites[site(st)], which_op(st); params(st)...) - t.op_cache[which_op(st) => site(st)] = op_tensor - end - if !isnothing(flux(op_tensor)) - q += flux(op_tensor) + q = QN() + for st in term + op_tensor = get(t.op_cache, which_op(st) => site(st), nothing) + if op_tensor === nothing + op_tensor = op(t.sites[site(st)], which_op(st); params(st)...) + t.op_cache[which_op(st) => site(st)] = op_tensor + end + if !isnothing(flux(op_tensor)) + q += flux(op_tensor) + end end - end - return q + return q end function ttn_svd( - coefficient_type::Type{<:Number}, os::OpSum, sites::IndsNetwork, root_vertex; kws... -) - term_qn_map = TermQNMap{vertextype(sites)}(sites) + coefficient_type::Type{<:Number}, os::OpSum, sites::IndsNetwork, root_vertex; kws... + ) + term_qn_map = TermQNMap{vertextype(sites)}(sites) - # Traverse tree outwards from root vertex - ordered_verts = _default_vertex_ordering(sites, root_vertex) - # Store edges in fixed ordering relative to root - ordered_edges = _default_edge_ordering(sites, root_vertex) + # Traverse tree outwards from root vertex + ordered_verts = _default_vertex_ordering(sites, root_vertex) + # Store edges in fixed ordering relative to root + ordered_edges = _default_edge_ordering(sites, root_vertex) - symbolic_ttn, inbond_coefs = make_symbolic_ttn( - coefficient_type, os, sites; ordered_verts, ordered_edges, root_vertex, term_qn_map - ) + symbolic_ttn, inbond_coefs = make_symbolic_ttn( + coefficient_type, os, sites; ordered_verts, ordered_edges, root_vertex, term_qn_map + ) - Vs = svd_bond_coefs( - coefficient_type, sites, inbond_coefs; ordered_verts, ordered_edges, kws... - ) + Vs = svd_bond_coefs( + coefficient_type, sites, inbond_coefs; ordered_verts, ordered_edges, kws... + ) - Hflux = -term_qn_map(terms(first(terms(os)))) + Hflux = -term_qn_map(terms(first(terms(os)))) - T = compress_ttn( - coefficient_type, sites, Hflux, symbolic_ttn, Vs; ordered_verts, ordered_edges - ) + T = compress_ttn( + coefficient_type, sites, Hflux, symbolic_ttn, Vs; ordered_verts, ordered_edges + ) - return T + return T end -# +# # Tree adaptations of functionalities in ITensors.jl/src/physics/autompo/opsum_to_mpo_generic.jl -# +# # TODO: fix fermion support, definitely broken # needed an extra `only` compared to ITensors version since IndsNetwork has Vector{<:Index} # as vertex data -function isfermionic(t::Vector{Op}, sites::IndsNetwork{V,<:Index}) where {V} - p = +1 - for op in t - if has_fermion_string(name(op), only(sites[site(op)])) - p *= -1 +function isfermionic(t::Vector{Op}, sites::IndsNetwork{V, <:Index}) where {V} + p = +1 + for op in t + if has_fermion_string(name(op), only(sites[site(op)])) + p *= -1 + end end - end - return (p == -1) + return (p == -1) end # only(site(ops[1])) in ITensors breaks for Tuple site labels, had to drop the only -function computeSiteProd(sites::IndsNetwork{V,<:Index}, ops::Prod{Op})::ITensor where {V} - v = site(ops[1]) - T = op(sites[v], which_op(ops[1]); params(ops[1])...) - for j in 2:length(ops) - (site(ops[j]) != v) && error("Mismatch of vertex labels in computeSiteProd") - opj = op(sites[v], which_op(ops[j]); params(ops[j])...) - T = product(T, opj) - end - return T +function computeSiteProd(sites::IndsNetwork{V, <:Index}, ops::Prod{Op})::ITensor where {V} + v = site(ops[1]) + T = op(sites[v], which_op(ops[1]); params(ops[1])...) + for j in 2:length(ops) + (site(ops[j]) != v) && error("Mismatch of vertex labels in computeSiteProd") + opj = op(sites[v], which_op(ops[j]); params(ops[j])...) + T = product(T, opj) + end + return T end function _default_vertex_ordering(g::AbstractGraph, root_vertex) - return reverse(post_order_dfs_vertices(g, root_vertex)) + return reverse(post_order_dfs_vertices(g, root_vertex)) end function _default_edge_ordering(g::AbstractGraph, root_vertex) - return reverse(reverse.(post_order_dfs_edges(g, root_vertex))) + return reverse(reverse.(post_order_dfs_edges(g, root_vertex))) end function check_terms_support(os::OpSum, sites) - for t in os - if !all(map(v -> has_vertex(sites, v), ITensors.sites(t))) - error( - "The OpSum contains a term $t that does not have support on the underlying graph." - ) + for t in os + if !all(map(v -> has_vertex(sites, v), ITensors.sites(t))) + error( + "The OpSum contains a term $t that does not have support on the underlying graph." + ) + end end - end + return end # This code is very similar to ITensorMPS sorteachterm in opsum_generic.jl function sorteachterm(os::OpSum, sites, root_vertex) - os = copy(os) - - # Build the isless_site function to pass to sortperm below: - # + ordering = array of vertices ordered relative to chosen root, chosen outward from root - # + site_positions = map from vertex to where it is in ordering (inverse map of `ordering`) - ordering = _default_vertex_ordering(sites, root_vertex) - site_positions = Dict(zip(ordering, 1:length(ordering))) - isless_site(o1::Op, o2::Op) = site_positions[site(o1)] < site_positions[site(o2)] - - N = nv(sites) - for j in eachindex(os) - t = os[j] - - # Sort operators in t by site order, - # and keep the permutation used, perm, for analysis below - Nt = length(t) - #perm = Vector{Int}(undef, Nt) - perm = sortperm(terms(t); alg=InsertionSort, lt=isless_site) - t = coefficient(t) * Prod(terms(t)[perm]) - - # Everything below deals with fermionic operators: - - # Identify fermionic operators, - # zeroing perm for bosonic operators, - # and inserting string "F" operators - prevsite = typemax(Int) #keep track of whether we are switching to a new site - t_parity = +1 - for n in reverse(1:Nt) - currsite = site(t[n]) - fermionic = has_fermion_string(which_op(t[n]), only(sites[site(t[n])])) - if !ITensors.using_auto_fermion() && (t_parity == -1) && (currsite < prevsite) - error("No verified fermion support for automatic TTN constructor!") # no verified support, just throw error - # Put local piece of Jordan-Wigner string emanating - # from fermionic operators to the right - # (Remaining F operators will be put in by svdMPO) - terms(t)[n] = Op("$(which_op(t[n])) * F", only(site(t[n]))) - end - prevsite = currsite - - if fermionic - t_parity = -t_parity - else - # Ignore bosonic operators in perm - # by zeroing corresponding entries - perm[n] = 0 - end - end - if t_parity == -1 - error("Parity-odd fermionic terms not yet supported by AutoTTN") - end + os = copy(os) + + # Build the isless_site function to pass to sortperm below: + # + ordering = array of vertices ordered relative to chosen root, chosen outward from root + # + site_positions = map from vertex to where it is in ordering (inverse map of `ordering`) + ordering = _default_vertex_ordering(sites, root_vertex) + site_positions = Dict(zip(ordering, 1:length(ordering))) + isless_site(o1::Op, o2::Op) = site_positions[site(o1)] < site_positions[site(o2)] + + N = nv(sites) + for j in eachindex(os) + t = os[j] + + # Sort operators in t by site order, + # and keep the permutation used, perm, for analysis below + Nt = length(t) + #perm = Vector{Int}(undef, Nt) + perm = sortperm(terms(t); alg = InsertionSort, lt = isless_site) + t = coefficient(t) * Prod(terms(t)[perm]) + + # Everything below deals with fermionic operators: + + # Identify fermionic operators, + # zeroing perm for bosonic operators, + # and inserting string "F" operators + prevsite = typemax(Int) #keep track of whether we are switching to a new site + t_parity = +1 + for n in reverse(1:Nt) + currsite = site(t[n]) + fermionic = has_fermion_string(which_op(t[n]), only(sites[site(t[n])])) + if !ITensors.using_auto_fermion() && (t_parity == -1) && (currsite < prevsite) + error("No verified fermion support for automatic TTN constructor!") # no verified support, just throw error + # Put local piece of Jordan-Wigner string emanating + # from fermionic operators to the right + # (Remaining F operators will be put in by svdMPO) + terms(t)[n] = Op("$(which_op(t[n])) * F", only(site(t[n]))) + end + prevsite = currsite + + if fermionic + t_parity = -t_parity + else + # Ignore bosonic operators in perm + # by zeroing corresponding entries + perm[n] = 0 + end + end + if t_parity == -1 + error("Parity-odd fermionic terms not yet supported by AutoTTN") + end - # Keep only fermionic op positions (non-zero entries) - filter!(!iszero, perm) - # and account for anti-commuting, fermionic operators - # during above sort; put resulting sign into coef - t *= ITensors.parity_sign(perm) - terms(os)[j] = t - end - return os + # Keep only fermionic op positions (non-zero entries) + filter!(!iszero, perm) + # and account for anti-commuting, fermionic operators + # during above sort; put resulting sign into coef + t *= ITensors.parity_sign(perm) + terms(os)[j] = t + end + return os end function sortmergeterms(os::OpSum{C}) where {C} - os_sorted_terms = sort(terms(os)) - os = Sum(os_sorted_terms) - # Merge (add) terms with same operators - merge_os_data = Scaled{C,Prod{Op}}[] - last_term = copy(os[1]) - last_term_coef = coefficient(last_term) - for n in 2:length(os) - if argument(os[n]) == argument(last_term) - last_term_coef += coefficient(os[n]) - last_term = last_term_coef * argument(last_term) - else - push!(merge_os_data, last_term) - last_term = os[n] - last_term_coef = coefficient(last_term) + os_sorted_terms = sort(terms(os)) + os = Sum(os_sorted_terms) + # Merge (add) terms with same operators + merge_os_data = Scaled{C, Prod{Op}}[] + last_term = copy(os[1]) + last_term_coef = coefficient(last_term) + for n in 2:length(os) + if argument(os[n]) == argument(last_term) + last_term_coef += coefficient(os[n]) + last_term = last_term_coef * argument(last_term) + else + push!(merge_os_data, last_term) + last_term = os[n] + last_term_coef = coefficient(last_term) + end end - end - push!(merge_os_data, last_term) - os = Sum(merge_os_data) - return os + push!(merge_os_data, last_term) + os = Sum(merge_os_data) + return os end """ @@ -631,65 +634,65 @@ end Convert an OpSum object `os` to a TreeTensorNetwork, with indices given by `sites`. """ function ttn( - os::OpSum, - sites::IndsNetwork; - root_vertex=GraphsExtensions.default_root_vertex(sites), - kwargs..., -) - length(terms(os)) == 0 && error("OpSum has no terms") - is_tree(sites) || error("Site index graph must be a tree.") - is_leaf_vertex(sites, root_vertex) || error("Tree root must be a leaf vertex.") - check_terms_support(os, sites) - os = deepcopy(os) #TODO: do we need this? sorteachterm copies `os` again - os = sorteachterm(os, sites, root_vertex) - os = sortmergeterms(os) - return ttn_svd(os, sites, root_vertex; kwargs...) + os::OpSum, + sites::IndsNetwork; + root_vertex = GraphsExtensions.default_root_vertex(sites), + kwargs..., + ) + length(terms(os)) == 0 && error("OpSum has no terms") + is_tree(sites) || error("Site index graph must be a tree.") + is_leaf_vertex(sites, root_vertex) || error("Tree root must be a leaf vertex.") + check_terms_support(os, sites) + os = deepcopy(os) #TODO: do we need this? sorteachterm copies `os` again + os = sorteachterm(os, sites, root_vertex) + os = sortmergeterms(os) + return ttn_svd(os, sites, root_vertex; kwargs...) end function mpo(os::OpSum, external_inds::Vector; kwargs...) - return ttn(os, path_indsnetwork(external_inds); kwargs...) + return ttn(os, path_indsnetwork(external_inds); kwargs...) end function mpo(os::OpSum, s::IndsNetwork; kwargs...) - # TODO: Check it is a path graph. - return ttn(os, s; kwargs...) + # TODO: Check it is a path graph. + return ttn(os, s; kwargs...) end # Conversion from other formats function ttn(o::Op, s::IndsNetwork; kwargs...) - return ttn(OpSum{Float64}() + o, s; kwargs...) + return ttn(OpSum{Float64}() + o, s; kwargs...) end -function ttn(o::Scaled{C,Op}, s::IndsNetwork; kwargs...) where {C} - return ttn(OpSum{C}() + o, s; kwargs...) +function ttn(o::Scaled{C, Op}, s::IndsNetwork; kwargs...) where {C} + return ttn(OpSum{C}() + o, s; kwargs...) end function ttn(o::Sum{Op}, s::IndsNetwork; kwargs...) - return ttn(OpSum{Float64}() + o, s; kwargs...) + return ttn(OpSum{Float64}() + o, s; kwargs...) end function ttn(o::Prod{Op}, s::IndsNetwork; kwargs...) - return ttn(OpSum{Float64}() + o, s; kwargs...) + return ttn(OpSum{Float64}() + o, s; kwargs...) end -function ttn(o::Scaled{C,Prod{Op}}, s::IndsNetwork; kwargs...) where {C} - return ttn(OpSum{C}() + o, s; kwargs...) +function ttn(o::Scaled{C, Prod{Op}}, s::IndsNetwork; kwargs...) where {C} + return ttn(OpSum{C}() + o, s; kwargs...) end -function ttn(o::Sum{Scaled{C,Op}}, s::IndsNetwork; kwargs...) where {C} - return ttn(OpSum{C}() + o, s; kwargs...) +function ttn(o::Sum{Scaled{C, Op}}, s::IndsNetwork; kwargs...) where {C} + return ttn(OpSum{C}() + o, s; kwargs...) end # Catch-all for leaf eltype specification function ttn(eltype::Type{<:Number}, os, sites::IndsNetwork; kwargs...) - return NDTensors.convert_scalartype(eltype, ttn(os, sites; kwargs...)) + return NDTensors.convert_scalartype(eltype, ttn(os, sites; kwargs...)) end -# +# # Sparse finite state machine construction -# +# # Allow sparse arrays with ITensors.Sum entries -function Base.zero(::Type{S}) where {S<:Sum} - return S() +function Base.zero(::Type{S}) where {S <: Sum} + return S() end Base.zero(t::Sum) = zero(typeof(t)) diff --git a/src/treetensornetworks/opsum_to_ttn/qnarrelem.jl b/src/treetensornetworks/opsum_to_ttn/qnarrelem.jl index 4f765f07..3664f97d 100644 --- a/src/treetensornetworks/opsum_to_ttn/qnarrelem.jl +++ b/src/treetensornetworks/opsum_to_ttn/qnarrelem.jl @@ -4,27 +4,27 @@ using StaticArrays: MVector # QNArrElem (sparse array with QNs) # ##################################### -struct QNArrElem{T,N} - qn_idxs::MVector{N,QN} - idxs::MVector{N,Int} - val::T +struct QNArrElem{T, N} + qn_idxs::MVector{N, QN} + idxs::MVector{N, Int} + val::T end -function Base.:(==)(a1::QNArrElem{T,N}, a2::QNArrElem{T,N})::Bool where {T,N} - return (a1.idxs == a2.idxs && a1.val == a2.val && a1.qn_idxs == a2.qn_idxs) +function Base.:(==)(a1::QNArrElem{T, N}, a2::QNArrElem{T, N})::Bool where {T, N} + return (a1.idxs == a2.idxs && a1.val == a2.val && a1.qn_idxs == a2.qn_idxs) end -function Base.isless(a1::QNArrElem{T,N}, a2::QNArrElem{T,N})::Bool where {T,N} - ###two separate loops s.t. the MPS case reduces to the ITensors Implementation of QNMatElem - for n in 1:N - if a1.qn_idxs[n] != a2.qn_idxs[n] - return a1.qn_idxs[n] < a2.qn_idxs[n] +function Base.isless(a1::QNArrElem{T, N}, a2::QNArrElem{T, N})::Bool where {T, N} + ###two separate loops s.t. the MPS case reduces to the ITensors Implementation of QNMatElem + for n in 1:N + if a1.qn_idxs[n] != a2.qn_idxs[n] + return a1.qn_idxs[n] < a2.qn_idxs[n] + end end - end - for n in 1:N - if a1.idxs[n] != a2.idxs[n] - return a1.idxs[n] < a2.idxs[n] + for n in 1:N + if a1.idxs[n] != a2.idxs[n] + return a1.idxs[n] < a2.idxs[n] + end end - end - return a1.val < a2.val + return a1.val < a2.val end diff --git a/src/treetensornetworks/projttns/abstractprojttn.jl b/src/treetensornetworks/projttns/abstractprojttn.jl index faaaf053..e999f883 100644 --- a/src/treetensornetworks/projttns/abstractprojttn.jl +++ b/src/treetensornetworks/projttns/abstractprojttn.jl @@ -31,124 +31,124 @@ on_edge(P::AbstractProjTTN) = isa(pos(P), edgetype(P)) nsite(P::AbstractProjTTN) = on_edge(P) ? 0 : length(pos(P)) function sites(P::AbstractProjTTN{V}) where {V} - on_edge(P) && return V[] - return pos(P) + on_edge(P) && return V[] + return pos(P) end function NamedGraphs.incident_edges(P::AbstractProjTTN{V}) where {V} - on_edge(P) && return [pos(P), reverse(pos(P))] - edges = [ - [edgetype(P)(n => v) for n in setdiff(neighbors(underlying_graph(P), v), sites(P))] for - v in sites(P) - ] - return collect(Base.Iterators.flatten(edges)) + on_edge(P) && return [pos(P), reverse(pos(P))] + edges = [ + [edgetype(P)(n => v) for n in setdiff(neighbors(underlying_graph(P), v), sites(P))] for + v in sites(P) + ] + return collect(Base.Iterators.flatten(edges)) end function internal_edges(P::AbstractProjTTN{V}) where {V} - on_edge(P) && return edgetype(P)[] - edges = [ - [edgetype(P)(v => n) for n in neighbors(underlying_graph(P), v) ∩ sites(P)] for - v in sites(P) - ] - return collect(Base.Iterators.flatten(edges)) + on_edge(P) && return edgetype(P)[] + edges = [ + [edgetype(P)(v => n) for n in neighbors(underlying_graph(P), v) ∩ sites(P)] for + v in sites(P) + ] + return collect(Base.Iterators.flatten(edges)) end environment(P::AbstractProjTTN, edge::Pair) = environment(P, edgetype(P)(edge)) function environment(P::AbstractProjTTN, edge::AbstractEdge) - return environments(P)[edge] + return environments(P)[edge] end # there has to be a better way to do this... function _separate_first(V::Vector) - sep = Base.Iterators.peel(V) - isnothing(sep) && return eltype(V)[], eltype(V)[] - return sep[1], collect(sep[2]) + sep = Base.Iterators.peel(V) + isnothing(sep) && return eltype(V)[], eltype(V)[] + return sep[1], collect(sep[2]) end function _separate_first_two(V::Vector) - frst, rst = _separate_first(V) - scnd, rst = _separate_first(rst) - return frst, scnd, rst + frst, rst = _separate_first(V) + scnd, rst = _separate_first(rst) + return frst, scnd, rst end projected_operator_tensors(P::AbstractProjTTN) = error("Not implemented.") function NDTensors.contract(P::AbstractProjTTN, v::ITensor) - return foldl(*, projected_operator_tensors(P); init=v) + return foldl(*, projected_operator_tensors(P); init = v) end function ITensors.product(P::AbstractProjTTN, v::ITensor) - Pv = contract(P, v) - if order(Pv) != order(v) - error( - string( - "The order of the ProjTTN-ITensor product P*v is not equal to the order of the ITensor v, ", - "this is probably due to an index mismatch.\nCommon reasons for this error: \n", - "(1) You are trying to multiply the ProjTTN with the $(nsite(P))-site wave-function at the wrong position.\n", - "(2) `orthogonalize!` was called, changing the MPS without updating the ProjTTN.\n\n", - "P*v inds: $(inds(Pv)) \n\n", - "v inds: $(inds(v))", - ), - ) - end - return noprime(Pv) + Pv = contract(P, v) + if order(Pv) != order(v) + error( + string( + "The order of the ProjTTN-ITensor product P*v is not equal to the order of the ITensor v, ", + "this is probably due to an index mismatch.\nCommon reasons for this error: \n", + "(1) You are trying to multiply the ProjTTN with the $(nsite(P))-site wave-function at the wrong position.\n", + "(2) `orthogonalize!` was called, changing the MPS without updating the ProjTTN.\n\n", + "P*v inds: $(inds(Pv)) \n\n", + "v inds: $(inds(v))", + ), + ) + end + return noprime(Pv) end (P::AbstractProjTTN)(v::ITensor) = product(P, v) function Base.eltype(P::AbstractProjTTN)::Type - ElType = eltype(operator(P)(first(sites(P)))) - for v in sites(P) - ElType = promote_type(ElType, eltype(operator(P)[v])) - end - for e in incident_edges(P) - ElType = promote_type(ElType, eltype(environments(P, e))) - end - return ElType + ElType = eltype(operator(P)(first(sites(P)))) + for v in sites(P) + ElType = promote_type(ElType, eltype(operator(P)[v])) + end + for e in incident_edges(P) + ElType = promote_type(ElType, eltype(environments(P, e))) + end + return ElType end NamedGraphs.vertextype(::Type{<:AbstractProjTTN{V}}) where {V} = V NamedGraphs.vertextype(p::AbstractProjTTN) = vertextype(typeof(p)) -function Base.size(P::AbstractProjTTN)::Tuple{Int,Int} - d = 1 - for e in incident_edges(P) - for i in inds(environment(P, e)) - plev(i) > 0 && (d *= dim(i)) +function Base.size(P::AbstractProjTTN)::Tuple{Int, Int} + d = 1 + for e in incident_edges(P) + for i in inds(environment(P, e)) + plev(i) > 0 && (d *= dim(i)) + end end - end - for j in sites(P) - for i in inds(operator(P)[j]) - plev(i) > 0 && (d *= dim(i)) + for j in sites(P) + for i in inds(operator(P)[j]) + plev(i) > 0 && (d *= dim(i)) + end end - end - return (d, d) + return (d, d) end function position(P::AbstractProjTTN, psi::AbstractTTN, pos) - P = shift_position(P, pos) - P = invalidate_environments(P) - P = make_environments(P, psi) - return P + P = shift_position(P, pos) + P = invalidate_environments(P) + P = make_environments(P, psi) + return P end function invalidate_environments(P::AbstractProjTTN) - ie = internal_edges(P) - newenvskeys = filter(!in(ie), keys(environments(P))) - P = set_environments(P, getindices_narrow_keytype(environments(P), newenvskeys)) - return P + ie = internal_edges(P) + newenvskeys = filter(!in(ie), keys(environments(P))) + P = set_environments(P, getindices_narrow_keytype(environments(P), newenvskeys)) + return P end function invalidate_environment(P::AbstractProjTTN, e::AbstractEdge) - T = typeof(environments(P)) - newenvskeys = filter(!isequal(e), keys(environments(P))) - P = set_environments(P, getindices_narrow_keytype(environments(P), newenvskeys)) - return P + T = typeof(environments(P)) + newenvskeys = filter(!isequal(e), keys(environments(P))) + P = set_environments(P, getindices_narrow_keytype(environments(P), newenvskeys)) + return P end function make_environments(P::AbstractProjTTN, psi::AbstractTTN) - for e in incident_edges(P) - P = make_environment(P, psi, e) - end - return P + for e in incident_edges(P) + P = make_environment(P, psi, e) + end + return P end diff --git a/src/treetensornetworks/projttns/projouterprodttn.jl b/src/treetensornetworks/projttns/projouterprodttn.jl index f3e50900..17210afa 100644 --- a/src/treetensornetworks/projttns/projouterprodttn.jl +++ b/src/treetensornetworks/projttns/projouterprodttn.jl @@ -4,10 +4,10 @@ using ITensors: ITensor using NamedGraphs.GraphsExtensions: incident_edges, is_leaf_vertex struct ProjOuterProdTTN{V} <: AbstractProjTTN{V} - pos::Union{Vector{<:V},NamedEdge{V}} - internal_state::TTN{V} - operator::TTN{V} - environments::Dictionary{NamedEdge{V},ITensor} + pos::Union{Vector{<:V}, NamedEdge{V}} + internal_state::TTN{V} + operator::TTN{V} + environments::Dictionary{NamedEdge{V}, ITensor} end environments(p::ProjOuterProdTTN) = p.environments @@ -17,102 +17,102 @@ pos(p::ProjOuterProdTTN) = p.pos internal_state(p::ProjOuterProdTTN) = p.internal_state function ProjOuterProdTTN(internal_state::AbstractTTN, operator::AbstractTTN) - return ProjOuterProdTTN( - vertextype(operator)[], - internal_state, - operator, - Dictionary{edgetype(operator),ITensor}(), - ) + return ProjOuterProdTTN( + vertextype(operator)[], + internal_state, + operator, + Dictionary{edgetype(operator), ITensor}(), + ) end function Base.copy(P::ProjOuterProdTTN) - return ProjOuterProdTTN( - pos(P), copy(internal_state(P)), copy(operator(P)), copy(environments(P)) - ) + return ProjOuterProdTTN( + pos(P), copy(internal_state(P)), copy(operator(P)), copy(environments(P)) + ) end function set_nsite(P::ProjOuterProdTTN, nsite) - return P + return P end function shift_position(P::ProjOuterProdTTN, pos) - return ProjOuterProdTTN(pos, internal_state(P), operator(P), environments(P)) + return ProjOuterProdTTN(pos, internal_state(P), operator(P), environments(P)) end function set_environments(p::ProjOuterProdTTN, environments) - return ProjOuterProdTTN(pos(p), internal_state(p), operator(p), environments) + return ProjOuterProdTTN(pos(p), internal_state(p), operator(p), environments) end set_environment(p::ProjOuterProdTTN, edge, env) = set_environment!(copy(p), edge, env) function set_environment!(p::ProjOuterProdTTN, edge, env) - set!(environments(p), edge, env) - return p + set!(environments(p), edge, env) + return p end function make_environment(P::ProjOuterProdTTN, state::AbstractTTN, e::AbstractEdge) - # invalidate environment for opposite edge direction if necessary - reverse(e) ∈ incident_edges(P) || (P = invalidate_environment(P, reverse(e))) - # do nothing if valid environment already present - if !haskey(environments(P), e) - if is_leaf_vertex(underlying_graph(P), src(e)) - # leaves are easy - env = internal_state(P)[src(e)] * operator(P)[src(e)] * dag(state[src(e)]) - else - # construct by contracting neighbors - neighbor_envs = ITensor[] - for n in setdiff(neighbors(underlying_graph(P), src(e)), [dst(e)]) - P = make_environment(P, state, edgetype(P)(n, src(e))) - push!(neighbor_envs, environment(P, edgetype(P)(n, src(e)))) - end - # manually heuristic for contraction order: two environments, site tensors, then - # other environments - frst, scnd, rst = _separate_first_two(neighbor_envs) - itensor_map = vcat( - internal_state(P)[src(e)], frst, scnd, operator(P)[src(e)], dag(state[src(e)]), rst - ) # no prime here in comparison to the same routine for Projttn - # TODO: actually use optimal contraction sequence here - env = reduce(*, itensor_map) + # invalidate environment for opposite edge direction if necessary + reverse(e) ∈ incident_edges(P) || (P = invalidate_environment(P, reverse(e))) + # do nothing if valid environment already present + if !haskey(environments(P), e) + if is_leaf_vertex(underlying_graph(P), src(e)) + # leaves are easy + env = internal_state(P)[src(e)] * operator(P)[src(e)] * dag(state[src(e)]) + else + # construct by contracting neighbors + neighbor_envs = ITensor[] + for n in setdiff(neighbors(underlying_graph(P), src(e)), [dst(e)]) + P = make_environment(P, state, edgetype(P)(n, src(e))) + push!(neighbor_envs, environment(P, edgetype(P)(n, src(e)))) + end + # manually heuristic for contraction order: two environments, site tensors, then + # other environments + frst, scnd, rst = _separate_first_two(neighbor_envs) + itensor_map = vcat( + internal_state(P)[src(e)], frst, scnd, operator(P)[src(e)], dag(state[src(e)]), rst + ) # no prime here in comparison to the same routine for Projttn + # TODO: actually use optimal contraction sequence here + env = reduce(*, itensor_map) + end + P = set_environment(P, e, env) end - P = set_environment(P, e, env) - end - @assert( - hascommoninds(environment(P, e), state[src(e)]), - "Something went wrong, probably re-orthogonalized this edge in the same direction twice!" - ) - return P + @assert( + hascommoninds(environment(P, e), state[src(e)]), + "Something went wrong, probably re-orthogonalized this edge in the same direction twice!" + ) + return P end function projected_operator_tensors(P::ProjOuterProdTTN) - environments = ITensor[environment(P, edge) for edge in incident_edges(P)] - # manual heuristic for contraction order fixing: for each site in ProjTTN, apply up to - # two environments, then TTN tensor, then other environments - itensor_map = ITensor[] - for j in sites(P) - push!(itensor_map, internal_state(P)[j]) - end - if on_edge(P) - append!(itensor_map, environments) - else - for s in sites(P) - site_envs = filter(hascommoninds(operator(P)[s]), environments) - frst, scnd, rst = _separate_first_two(site_envs) - site_tensors = vcat(frst, scnd, operator(P)[s], rst) - append!(itensor_map, site_tensors) + environments = ITensor[environment(P, edge) for edge in incident_edges(P)] + # manual heuristic for contraction order fixing: for each site in ProjTTN, apply up to + # two environments, then TTN tensor, then other environments + itensor_map = ITensor[] + for j in sites(P) + push!(itensor_map, internal_state(P)[j]) + end + if on_edge(P) + append!(itensor_map, environments) + else + for s in sites(P) + site_envs = filter(hascommoninds(operator(P)[s]), environments) + frst, scnd, rst = _separate_first_two(site_envs) + site_tensors = vcat(frst, scnd, operator(P)[s], rst) + append!(itensor_map, site_tensors) + end end - end - return itensor_map + return itensor_map end function contract_ket(P::ProjOuterProdTTN, v::ITensor) - itensor_map = projected_operator_tensors(P) - for t in itensor_map - v *= t - end - return v + itensor_map = projected_operator_tensors(P) + for t in itensor_map + v *= t + end + return v end # ToDo: verify conjugation etc. with complex AbstractTTN function NDTensors.contract(P::ProjOuterProdTTN, x::ITensor) - ket = contract_ket(P, ITensor(one(Bool))) - return (dag(ket) * x) * ket + ket = contract_ket(P, ITensor(one(Bool))) + return (dag(ket) * x) * ket end diff --git a/src/treetensornetworks/projttns/projttn.jl b/src/treetensornetworks/projttns/projttn.jl index f4e1f200..70f92001 100644 --- a/src/treetensornetworks/projttns/projttn.jl +++ b/src/treetensornetworks/projttns/projttn.jl @@ -8,32 +8,32 @@ using NamedGraphs.GraphsExtensions: incident_edges, is_leaf_vertex """ ProjTTN """ -struct ProjTTN{V,Pos<:Union{Indices{V},NamedEdge{V}}} <: AbstractProjTTN{V} - pos::Pos - operator::TTN{V} - environments::Dictionary{NamedEdge{V},ITensor} - global function _ProjTTN(pos, operator::TTN, environments::Dictionary) - return new{vertextype(operator),Indices{vertextype(operator)}}( - Indices{vertextype(operator)}(pos), - operator, - convert(Dictionary{NamedEdge{vertextype(operator)},ITensor}, environments), - ) - end - global function _ProjTTN(pos::NamedEdge, operator::TTN, environments::Dictionary) - return new{vertextype(operator),NamedEdge{vertextype(operator)}}( - convert(NamedEdge{vertextype(operator)}, pos), - operator, - convert(Dictionary{NamedEdge{vertextype(operator)},ITensor}, environments), - ) - end +struct ProjTTN{V, Pos <: Union{Indices{V}, NamedEdge{V}}} <: AbstractProjTTN{V} + pos::Pos + operator::TTN{V} + environments::Dictionary{NamedEdge{V}, ITensor} + global function _ProjTTN(pos, operator::TTN, environments::Dictionary) + return new{vertextype(operator), Indices{vertextype(operator)}}( + Indices{vertextype(operator)}(pos), + operator, + convert(Dictionary{NamedEdge{vertextype(operator)}, ITensor}, environments), + ) + end + global function _ProjTTN(pos::NamedEdge, operator::TTN, environments::Dictionary) + return new{vertextype(operator), NamedEdge{vertextype(operator)}}( + convert(NamedEdge{vertextype(operator)}, pos), + operator, + convert(Dictionary{NamedEdge{vertextype(operator)}, ITensor}, environments), + ) + end end function ProjTTN(pos, operator::TTN, environments::Dictionary) - return _ProjTTN(pos, operator, environments) + return _ProjTTN(pos, operator, environments) end function ProjTTN(operator::TTN) - return ProjTTN(vertices(operator), operator, Dictionary{edgetype(operator),ITensor}()) + return ProjTTN(vertices(operator), operator, Dictionary{edgetype(operator), ITensor}()) end Base.copy(P::ProjTTN) = ProjTTN(pos(P), copy(operator(P)), copy(environments(P))) @@ -47,67 +47,67 @@ pos(P::ProjTTN) = P.pos # trivial if we choose to specify position as above; only kept to allow using alongside # ProjMPO function set_nsite(P::ProjTTN, nsite) - return P + return P end function shift_position(P::ProjTTN, pos) - return ProjTTN(pos, operator(P), environments(P)) + return ProjTTN(pos, operator(P), environments(P)) end set_environments(p::ProjTTN, environments) = ProjTTN(pos(p), operator(p), environments) set_environment(p::ProjTTN, edge, env) = set_environment!(copy(p), edge, env) function set_environment!(p::ProjTTN, edge, env) - set!(environments(p), edge, env) - return p + set!(environments(p), edge, env) + return p end function make_environment(P::ProjTTN, state::AbstractTTN, e::AbstractEdge) - # invalidate environment for opposite edge direction if necessary - reverse(e) ∈ incident_edges(P) || (P = invalidate_environment(P, reverse(e))) - # do nothing if valid environment already present - if !haskey(environments(P), e) - if is_leaf_vertex(underlying_graph(P), src(e)) - # leaves are easy - env = state[src(e)] * operator(P)[src(e)] * dag(prime(state[src(e)])) - else - # construct by contracting neighbors - neighbor_envs = ITensor[] - for n in setdiff(neighbors(underlying_graph(P), src(e)), [dst(e)]) - P = make_environment(P, state, edgetype(P)(n, src(e))) - push!(neighbor_envs, environment(P, edgetype(P)(n, src(e)))) - end - # manually heuristic for contraction order: two environments, site tensors, then - # other environments - frst, scnd, rst = _separate_first_two(neighbor_envs) - itensor_map = vcat( - state[src(e)], frst, scnd, operator(P)[src(e)], dag(prime(state[src(e)])), rst - ) - # TODO: actually use optimal contraction sequence here - env = reduce(*, itensor_map) + # invalidate environment for opposite edge direction if necessary + reverse(e) ∈ incident_edges(P) || (P = invalidate_environment(P, reverse(e))) + # do nothing if valid environment already present + if !haskey(environments(P), e) + if is_leaf_vertex(underlying_graph(P), src(e)) + # leaves are easy + env = state[src(e)] * operator(P)[src(e)] * dag(prime(state[src(e)])) + else + # construct by contracting neighbors + neighbor_envs = ITensor[] + for n in setdiff(neighbors(underlying_graph(P), src(e)), [dst(e)]) + P = make_environment(P, state, edgetype(P)(n, src(e))) + push!(neighbor_envs, environment(P, edgetype(P)(n, src(e)))) + end + # manually heuristic for contraction order: two environments, site tensors, then + # other environments + frst, scnd, rst = _separate_first_two(neighbor_envs) + itensor_map = vcat( + state[src(e)], frst, scnd, operator(P)[src(e)], dag(prime(state[src(e)])), rst + ) + # TODO: actually use optimal contraction sequence here + env = reduce(*, itensor_map) + end + P = set_environment(P, e, env) end - P = set_environment(P, e, env) - end - @assert( - hascommoninds(environment(P, e), state[src(e)]), - "Something went wrong, probably re-orthogonalized this edge in the same direction twice!" - ) - return P + @assert( + hascommoninds(environment(P, e), state[src(e)]), + "Something went wrong, probably re-orthogonalized this edge in the same direction twice!" + ) + return P end function projected_operator_tensors(P::ProjTTN) - environments = ITensor[environment(P, edge) for edge in incident_edges(P)] - # manual heuristic for contraction order fixing: for each site in ProjTTN, apply up to - # two environments, then TTN tensor, then other environments - if on_edge(P) - itensor_map = environments - else - itensor_map = ITensor[] - for s in sites(P) - site_envs = filter(hascommoninds(operator(P)[s]), environments) - frst, scnd, rst = _separate_first_two(site_envs) - site_tensors = vcat(frst, scnd, operator(P)[s], rst) - append!(itensor_map, site_tensors) + environments = ITensor[environment(P, edge) for edge in incident_edges(P)] + # manual heuristic for contraction order fixing: for each site in ProjTTN, apply up to + # two environments, then TTN tensor, then other environments + if on_edge(P) + itensor_map = environments + else + itensor_map = ITensor[] + for s in sites(P) + site_envs = filter(hascommoninds(operator(P)[s]), environments) + frst, scnd, rst = _separate_first_two(site_envs) + site_tensors = vcat(frst, scnd, operator(P)[s], rst) + append!(itensor_map, site_tensors) + end end - end - return itensor_map + return itensor_map end diff --git a/src/treetensornetworks/projttns/projttnsum.jl b/src/treetensornetworks/projttns/projttnsum.jl index ceff7d6d..2cef15de 100644 --- a/src/treetensornetworks/projttns/projttnsum.jl +++ b/src/treetensornetworks/projttns/projttnsum.jl @@ -6,12 +6,12 @@ using NamedGraphs.GraphsExtensions: incident_edges """ ProjTTNSum """ -struct ProjTTNSum{V,T<:AbstractProjTTN{V},Z<:Number} <: AbstractProjTTN{V} - terms::Vector{T} - factors::Vector{Z} - function ProjTTNSum(terms::Vector{<:AbstractProjTTN}, factors::Vector{<:Number}) - return new{vertextype(eltype(terms)),eltype(terms),eltype(factors)}(terms, factors) - end +struct ProjTTNSum{V, T <: AbstractProjTTN{V}, Z <: Number} <: AbstractProjTTN{V} + terms::Vector{T} + factors::Vector{Z} + function ProjTTNSum(terms::Vector{<:AbstractProjTTN}, factors::Vector{<:Number}) + return new{vertextype(eltype(terms)), eltype(terms), eltype(factors)}(terms, factors) + end end LazyApply.terms(P::ProjTTNSum) = P.terms @@ -20,10 +20,10 @@ factors(P::ProjTTNSum) = P.factors Base.copy(P::ProjTTNSum) = ProjTTNSum(copy.(terms(P)), copy(factors(P))) function ProjTTNSum(operators::Vector{<:AbstractProjTTN}) - return ProjTTNSum(operators, fill(one(Bool), length(operators))) + return ProjTTNSum(operators, fill(one(Bool), length(operators))) end function ProjTTNSum(operators::Vector{<:AbstractTTN}) - return ProjTTNSum(ProjTTN.(operators)) + return ProjTTNSum(ProjTTN.(operators)) end on_edge(P::ProjTTNSum) = on_edge(terms(P)[1]) @@ -31,7 +31,7 @@ on_edge(P::ProjTTNSum) = on_edge(terms(P)[1]) nsite(P::ProjTTNSum) = nsite(terms(P)[1]) function set_nsite(Ps::ProjTTNSum, nsite) - return ProjTTNSum(map(p -> set_nsite(p, nsite), terms(Ps)), factors(Ps)) + return ProjTTNSum(map(p -> set_nsite(p, nsite), terms(Ps)), factors(Ps)) end DataGraphs.underlying_graph(P::ProjTTNSum) = underlying_graph(terms(P)[1]) @@ -47,21 +47,21 @@ internal_edges(P::ProjTTNSum) = internal_edges(terms(P)[1]) ITensors.product(P::ProjTTNSum, v::ITensor) = noprime(contract(P, v)) function ITensors.contract(P::ProjTTNSum, v::ITensor) - res = mapreduce(+, zip(factors(P), terms(P))) do (f, p) - f * contract(p, v) - end - return res + res = mapreduce(+, zip(factors(P), terms(P))) do (f, p) + f * contract(p, v) + end + return res end function contract_ket(P::ProjTTNSum, v::ITensor) - res = mapreduce(+, zip(factors(P), terms(P))) do (f, p) - f * contract_ket(p, v) - end - return res + res = mapreduce(+, zip(factors(P), terms(P))) do (f, p) + f * contract_ket(p, v) + end + return res end function Base.eltype(P::ProjTTNSum) - return mapreduce(eltype, promote_type, terms(P)) + return mapreduce(eltype, promote_type, terms(P)) end (P::ProjTTNSum)(v::ITensor) = product(P, v) @@ -69,8 +69,8 @@ end Base.size(P::ProjTTNSum) = size(terms(P)[1]) function position(P::ProjTTNSum, psi::AbstractTTN, pos) - theterms = map(M -> position(M, psi, pos), terms(P)) - #@show typeof(theterms) - #@show factors(P) - return ProjTTNSum(theterms, factors(P)) + theterms = map(M -> position(M, psi, pos), terms(P)) + #@show typeof(theterms) + #@show factors(P) + return ProjTTNSum(theterms, factors(P)) end diff --git a/src/treetensornetworks/treetensornetwork.jl b/src/treetensornetworks/treetensornetwork.jl index cc50230e..402539ed 100644 --- a/src/treetensornetworks/treetensornetwork.jl +++ b/src/treetensornetworks/treetensornetwork.jl @@ -8,27 +8,27 @@ using NamedGraphs.GraphsExtensions: GraphsExtensions, vertextype TreeTensorNetwork{V} <: AbstractTreeTensorNetwork{V} """ struct TreeTensorNetwork{V} <: AbstractTreeTensorNetwork{V} - tensornetwork::ITensorNetwork{V} - ortho_region::Indices{V} - global function _TreeTensorNetwork(tensornetwork::ITensorNetwork, ortho_region::Indices) - @assert is_tree(tensornetwork) - return new{vertextype(tensornetwork)}(tensornetwork, ortho_region) - end + tensornetwork::ITensorNetwork{V} + ortho_region::Indices{V} + global function _TreeTensorNetwork(tensornetwork::ITensorNetwork, ortho_region::Indices) + @assert is_tree(tensornetwork) + return new{vertextype(tensornetwork)}(tensornetwork, ortho_region) + end end function _TreeTensorNetwork(tensornetwork::ITensorNetwork, ortho_region) - return _TreeTensorNetwork(tensornetwork, Indices(ortho_region)) + return _TreeTensorNetwork(tensornetwork, Indices(ortho_region)) end function _TreeTensorNetwork(tensornetwork::ITensorNetwork) - return _TreeTensorNetwork(tensornetwork, vertices(tensornetwork)) + return _TreeTensorNetwork(tensornetwork, vertices(tensornetwork)) end -function TreeTensorNetwork(tn::ITensorNetwork; ortho_region=vertices(tn)) - return _TreeTensorNetwork(tn, ortho_region) +function TreeTensorNetwork(tn::ITensorNetwork; ortho_region = vertices(tn)) + return _TreeTensorNetwork(tn, ortho_region) end function TreeTensorNetwork{V}(tn::ITensorNetwork) where {V} - return TreeTensorNetwork(ITensorNetwork{V}(tn)) + return TreeTensorNetwork(ITensorNetwork{V}(tn)) end const TTN = TreeTensorNetwork @@ -41,81 +41,81 @@ ortho_region(tn::TTN) = getfield(tn, :ortho_region) data_graph(tn::TTN) = data_graph(ITensorNetwork(tn)) function data_graph_type(G::Type{<:TTN}) - return data_graph_type(fieldtype(G, :tensornetwork)) + return data_graph_type(fieldtype(G, :tensornetwork)) end function Base.copy(tn::TTN) - return _TreeTensorNetwork(copy(ITensorNetwork(tn)), copy(ortho_region(tn))) + return _TreeTensorNetwork(copy(ITensorNetwork(tn)), copy(ortho_region(tn))) end -# +# # Constructor -# +# function set_ortho_region(tn::TTN, ortho_region) - return ttn(ITensorNetwork(tn); ortho_region) + return ttn(ITensorNetwork(tn); ortho_region) end -function ttn(args...; ortho_region=nothing) - tn = ITensorNetwork(args...) - if isnothing(ortho_region) - ortho_region = vertices(tn) - end - return _TreeTensorNetwork(tn, ortho_region) +function ttn(args...; ortho_region = nothing) + tn = ITensorNetwork(args...) + if isnothing(ortho_region) + ortho_region = vertices(tn) + end + return _TreeTensorNetwork(tn, ortho_region) end -function mps(args...; ortho_region=nothing) - # TODO: Check it is a path graph. - tn = ITensorNetwork(args...) - if isnothing(ortho_region) - ortho_region = vertices(tn) - end - return _TreeTensorNetwork(tn, ortho_region) +function mps(args...; ortho_region = nothing) + # TODO: Check it is a path graph. + tn = ITensorNetwork(args...) + if isnothing(ortho_region) + ortho_region = vertices(tn) + end + return _TreeTensorNetwork(tn, ortho_region) end function mps(f, is::Vector{<:Index}; kwargs...) - return mps(f, path_indsnetwork(is); kwargs...) + return mps(f, path_indsnetwork(is); kwargs...) end # Construct from dense ITensor, using IndsNetwork of site indices. function ttn( - a::ITensor, - is::IndsNetwork; - ortho_region=Indices([GraphsExtensions.default_root_vertex(is)]), - kwargs..., -) - for v in vertices(is) - @assert hasinds(a, is[v]) - end - @assert ortho_region ⊆ vertices(is) - tn = ITensorNetwork(is) - ortho_center = first(ortho_region) - for e in post_order_dfs_edges(tn, ortho_center) - left_inds = uniqueinds(is, e) - a_l, a_r = factorize(a, left_inds; tags=edge_tag(e), ortho="left", kwargs...) - tn[src(e)] = a_l - is[e] = commoninds(a_l, a_r) - a = a_r - end - tn[ortho_center] = a - ttn_a = ttn(tn) - return orthogonalize(ttn_a, ortho_center) + a::ITensor, + is::IndsNetwork; + ortho_region = Indices([GraphsExtensions.default_root_vertex(is)]), + kwargs..., + ) + for v in vertices(is) + @assert hasinds(a, is[v]) + end + @assert ortho_region ⊆ vertices(is) + tn = ITensorNetwork(is) + ortho_center = first(ortho_region) + for e in post_order_dfs_edges(tn, ortho_center) + left_inds = uniqueinds(is, e) + a_l, a_r = factorize(a, left_inds; tags = edge_tag(e), ortho = "left", kwargs...) + tn[src(e)] = a_l + is[e] = commoninds(a_l, a_r) + a = a_r + end + tn[ortho_center] = a + ttn_a = ttn(tn) + return orthogonalize(ttn_a, ortho_center) end function random_ttn(args...; kwargs...) - # TODO: Check it is a tree graph. - return normalize(_TreeTensorNetwork(random_tensornetwork(args...; kwargs...))) + # TODO: Check it is a tree graph. + return normalize(_TreeTensorNetwork(random_tensornetwork(args...; kwargs...))) end function random_mps(args...; kwargs...) - # TODO: Check it is a path graph. - return random_ttn(args...; kwargs...) + # TODO: Check it is a path graph. + return random_ttn(args...; kwargs...) end function random_mps(f, is::Vector{<:Index}; kwargs...) - return random_mps(f, path_indsnetwork(is); kwargs...) + return random_mps(f, path_indsnetwork(is); kwargs...) end function random_mps(s::Vector{<:Index}; kwargs...) - return random_mps(path_indsnetwork(s); kwargs...) + return random_mps(path_indsnetwork(s); kwargs...) end diff --git a/src/update_observer.jl b/src/update_observer.jl index d558310e..a090f240 100644 --- a/src/update_observer.jl +++ b/src/update_observer.jl @@ -1,5 +1,5 @@ function update_observer!(observer; kwargs...) - return error("Not implemented") + return error("Not implemented") end # Default fallback if no observer is specified. @@ -7,26 +7,26 @@ end # maybe it is a bit too "tricky" and should be # removed. function update_observer!(observer::Nothing; kwargs...) - return nothing + return nothing end -struct ComposedObservers{Observers<:Tuple} - observers::Observers +struct ComposedObservers{Observers <: Tuple} + observers::Observers end compose_observers(observers...) = ComposedObservers(observers) function update_observer!(observer::ComposedObservers; kwargs...) - for observerᵢ in observer.observers - update_observer!(observerᵢ; kwargs...) - end - return observer + for observerᵢ in observer.observers + update_observer!(observerᵢ; kwargs...) + end + return observer end -struct ValuesObserver{Values<:NamedTuple} - values::Values +struct ValuesObserver{Values <: NamedTuple} + values::Values end function update_observer!(observer::ValuesObserver; kwargs...) - for key in keys(observer.values) - observer.values[key][] = kwargs[key] - end - return observer + for key in keys(observer.values) + observer.values[key][] = kwargs[key] + end + return observer end diff --git a/src/utils.jl b/src/utils.jl index c1e1cde0..b73e6ab5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,29 +1,29 @@ using Dictionaries: getindices function cartesian_to_linear(dims::Tuple) - return Dictionary(vec(Tuple.(CartesianIndices(dims))), 1:prod(dims)) + return Dictionary(vec(Tuple.(CartesianIndices(dims))), 1:prod(dims)) end -front(itr, n=1) = Iterators.take(itr, length(itr) - n) +front(itr, n = 1) = Iterators.take(itr, length(itr) - n) tail(itr) = Iterators.drop(itr, 1) # Tree utils function line_to_tree(line::Vector) - if length(line) == 1 && line[1] isa Vector - return line[1] - end - if length(line) <= 2 - return line - end - return [line_to_tree(line[1:(end - 1)]), line[end]] + if length(line) == 1 && line[1] isa Vector + return line[1] + end + if length(line) <= 2 + return line + end + return [line_to_tree(line[1:(end - 1)]), line[end]] end # Pad with last value to length or truncate to length. # If it is a single value (non-Vector), fill with # that value to the length. function extend_or_truncate(x::Vector, length::Int) - l = length - Base.length(x) - return l >= 0 ? [x; fill(last(x), l)] : x[1:length] + l = length - Base.length(x) + return l >= 0 ? [x; fill(last(x), l)] : x[1:length] end extend_or_truncate(x, length::Int) = extend_or_truncate([x], length) @@ -36,56 +36,56 @@ struct AbstractArrayLeafStyle <: WalkStyle end StructWalk.children(::AbstractArrayLeafStyle, x::AbstractArray) = () function extend_or_truncate_columns(nt::NamedTuple, length::Int) - return map(x -> extend_or_truncate(x, length), nt) + return map(x -> extend_or_truncate(x, length), nt) end function extend_or_truncate_columns_recursive(nt::NamedTuple, length::Int) - return postwalk(AbstractArrayLeafStyle(), nt) do x - x isa NamedTuple && return x + return postwalk(AbstractArrayLeafStyle(), nt) do x + x isa NamedTuple && return x - return extend_or_truncate(x, length) - end + return extend_or_truncate(x, length) + end end #ToDo: remove #nrows(nt::NamedTuple) = isempty(nt) ? 0 : length(first(nt)) function row(nt::NamedTuple, i::Int) - isempty(nt) ? (return nt) : (return map(x -> x[i], nt)) + return isempty(nt) ? (return nt) : (return map(x -> x[i], nt)) end # Similar to `Tables.rowtable(x)` function rows(nt::NamedTuple, length::Int) - return [row(nt, i) for i in 1:length] + return [row(nt, i) for i in 1:length] end function rows_recursive(nt::NamedTuple, length::Int) - return postwalk(AbstractArrayLeafStyle(), nt) do x - !(x isa NamedTuple) && return x + return postwalk(AbstractArrayLeafStyle(), nt) do x + !(x isa NamedTuple) && return x - return rows(x, length) - end + return rows(x, length) + end end function expand(nt::NamedTuple, length::Int) - nt_padded = extend_or_truncate_columns_recursive(nt, length) - return rows_recursive(nt_padded, length) + nt_padded = extend_or_truncate_columns_recursive(nt, length) + return rows_recursive(nt_padded, length) end function interleave(a::Vector, b::Vector) - ab = flatten(collect(zip(a, b))) - if length(a) == length(b) - return ab - elseif length(a) == length(b) + 1 - return append!(ab, [last(a)]) - else - error( - "Trying to interleave vectors of length $(length(a)) and $(length(b)), not implemented.", - ) - end + ab = flatten(collect(zip(a, b))) + if length(a) == length(b) + return ab + elseif length(a) == length(b) + 1 + return append!(ab, [last(a)]) + else + error( + "Trying to interleave vectors of length $(length(a)) and $(length(b)), not implemented.", + ) + end end function getindices_narrow_keytype(d::Dictionary, indices) - return convert(typeof(d), getindices(d, indices)) + return convert(typeof(d), getindices(d, indices)) end diff --git a/src/visualize.jl b/src/visualize.jl index 5599ea3c..7e72d4c3 100644 --- a/src/visualize.jl +++ b/src/visualize.jl @@ -3,26 +3,26 @@ using Graphs: vertices using NamedGraphs: NamedGraphs, AbstractNamedGraph using ITensors.ITensorVisualizationCore: ITensorVisualizationCore function ITensorVisualizationCore.visualize( - graph::AbstractNamedGraph, - args...; - vertex_labels_prefix=nothing, - vertex_labels=nothing, - kwargs..., -) - if !isnothing(vertex_labels_prefix) - vertex_labels = [vertex_labels_prefix * string(v) for v in vertices(graph)] - end - #edge_labels = [string(e) for e in edges(graph)] - return ITensorVisualizationCore.visualize( - NamedGraphs.position_graph(graph), args...; vertex_labels, kwargs... - ) + graph::AbstractNamedGraph, + args...; + vertex_labels_prefix = nothing, + vertex_labels = nothing, + kwargs..., + ) + if !isnothing(vertex_labels_prefix) + vertex_labels = [vertex_labels_prefix * string(v) for v in vertices(graph)] + end + #edge_labels = [string(e) for e in edges(graph)] + return ITensorVisualizationCore.visualize( + NamedGraphs.position_graph(graph), args...; vertex_labels, kwargs... + ) end # TODO: Move to `DataGraphsITensorVisualizationCoreExt`. using DataGraphs: DataGraphs, AbstractDataGraph using ITensors.ITensorVisualizationCore: ITensorVisualizationCore function ITensorVisualizationCore.visualize(graph::AbstractDataGraph, args...; kwargs...) - return ITensorVisualizationCore.visualize( - DataGraphs.underlying_graph(graph), args...; kwargs... - ) + return ITensorVisualizationCore.visualize( + DataGraphs.underlying_graph(graph), args...; kwargs... + ) end diff --git a/test/ITensorNetworksTestSolversUtils/solvers.jl b/test/ITensorNetworksTestSolversUtils/solvers.jl index 9b9568be..43407efd 100644 --- a/test/ITensorNetworksTestSolversUtils/solvers.jl +++ b/test/ITensorNetworksTestSolversUtils/solvers.jl @@ -4,34 +4,34 @@ using KrylovKit: exponentiate using OrdinaryDiffEqTsit5: ODEProblem, Tsit5, solve function ode_solver( - H::TimeDependentSum, - time_step, - ψ₀; - current_time=0.0, - outputlevel=0, - solver_alg=Tsit5(), - kwargs..., -) - if outputlevel ≥ 3 - println(" In ODE solver, current_time = $current_time, time_step = $time_step") - end + H::TimeDependentSum, + time_step, + ψ₀; + current_time = 0.0, + outputlevel = 0, + solver_alg = Tsit5(), + kwargs..., + ) + if outputlevel ≥ 3 + println(" In ODE solver, current_time = $current_time, time_step = $time_step") + end - time_span = (current_time, current_time + time_step) - u₀, itensor_from_vec = to_vec(ψ₀) - f(ψ::ITensor, p, t) = H(t)(ψ) - f(u::Vector, p, t) = to_vec(f(itensor_from_vec(u), p, t))[1] - prob = ODEProblem(f, u₀, time_span) - sol = solve(prob, solver_alg; kwargs...) - uₜ = sol.u[end] - return itensor_from_vec(uₜ), nothing + time_span = (current_time, current_time + time_step) + u₀, itensor_from_vec = to_vec(ψ₀) + f(ψ::ITensor, p, t) = H(t)(ψ) + f(u::Vector, p, t) = to_vec(f(itensor_from_vec(u), p, t))[1] + prob = ODEProblem(f, u₀, time_span) + sol = solve(prob, solver_alg; kwargs...) + uₜ = sol.u[end] + return itensor_from_vec(uₜ), nothing end function krylov_solver( - H::TimeDependentSum, time_step, ψ₀; current_time=0.0, outputlevel=0, kwargs... -) - if outputlevel ≥ 3 - println(" In Krylov solver, current_time = $current_time, time_step = $time_step") - end - ψₜ, info = exponentiate(H(current_time), time_step, ψ₀; kwargs...) - return ψₜ, info + H::TimeDependentSum, time_step, ψ₀; current_time = 0.0, outputlevel = 0, kwargs... + ) + if outputlevel ≥ 3 + println(" In Krylov solver, current_time = $current_time, time_step = $time_step") + end + ψₜ, info = exponentiate(H(current_time), time_step, ψ₀; kwargs...) + return ψₜ, info end diff --git a/test/runtests.jl b/test/runtests.jl index 98b2d2b8..00080503 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,60 +6,62 @@ using Suppressor: Suppressor const pat = r"(?:--group=)(\w+)" arg_id = findfirst(contains(pat), ARGS) const GROUP = uppercase( - if isnothing(arg_id) - get(ENV, "GROUP", "ALL") - else - only(match(pat, ARGS[arg_id]).captures) - end, + if isnothing(arg_id) + get(ENV, "GROUP", "ALL") + else + only(match(pat, ARGS[arg_id]).captures) + end, ) "match files of the form `test_*.jl`, but exclude `*setup*.jl`" function istestfile(fn) - return endswith(fn, ".jl") && startswith(basename(fn), "test_") && !contains(fn, "setup") + return endswith(fn, ".jl") && startswith(basename(fn), "test_") && !contains(fn, "setup") end "match files of the form `*.jl`, but exclude `*_notest.jl` and `*setup*.jl`" function isexamplefile(fn) - return endswith(fn, ".jl") && !endswith(fn, "_notest.jl") && !contains(fn, "setup") + return endswith(fn, ".jl") && !endswith(fn, "_notest.jl") && !contains(fn, "setup") end @time begin - # tests in groups based on folder structure - for testgroup in filter(isdir, readdir(@__DIR__)) - if GROUP == "ALL" || GROUP == uppercase(testgroup) - groupdir = joinpath(@__DIR__, testgroup) - for file in filter(istestfile, readdir(groupdir)) - filename = joinpath(groupdir, file) - @eval @safetestset $file begin - include($filename) + # tests in groups based on folder structure + for testgroup in filter(isdir, readdir(@__DIR__)) + if GROUP == "ALL" || GROUP == uppercase(testgroup) + groupdir = joinpath(@__DIR__, testgroup) + for file in filter(istestfile, readdir(groupdir)) + filename = joinpath(groupdir, file) + @eval @safetestset $file begin + include($filename) + end + end end - end end - end - # single files in top folder - for file in filter(istestfile, readdir(@__DIR__)) - (file == basename(@__FILE__)) && continue # exclude this file to avoid infinite recursion - @eval @safetestset $file begin - include($file) + # single files in top folder + for file in filter(istestfile, readdir(@__DIR__)) + (file == basename(@__FILE__)) && continue # exclude this file to avoid infinite recursion + @eval @safetestset $file begin + include($file) + end end - end - # test examples - examplepath = joinpath(@__DIR__, "..", "examples") - for (root, _, files) in walkdir(examplepath) - contains(chopprefix(root, @__DIR__), "setup") && continue - for file in filter(isexamplefile, files) - filename = joinpath(root, file) - @eval begin - @safetestset $file begin - $(Expr( - :macrocall, - GlobalRef(Suppressor, Symbol("@suppress")), - LineNumberNode(@__LINE__, @__FILE__), - :(include($filename)), - )) + # test examples + examplepath = joinpath(@__DIR__, "..", "examples") + for (root, _, files) in walkdir(examplepath) + contains(chopprefix(root, @__DIR__), "setup") && continue + for file in filter(isexamplefile, files) + filename = joinpath(root, file) + @eval begin + @safetestset $file begin + $( + Expr( + :macrocall, + GlobalRef(Suppressor, Symbol("@suppress")), + LineNumberNode(@__LINE__, @__FILE__), + :(include($filename)), + ) + ) + end + end end - end end - end end diff --git a/test/test_abstractgraph.jl b/test/test_abstractgraph.jl index 2a67af60..6b8d5e7c 100644 --- a/test/test_abstractgraph.jl +++ b/test/test_abstractgraph.jl @@ -5,15 +5,15 @@ using NamedGraphs.GraphsExtensions: root_vertex, is_rooted, is_binary_arborescen using Test: @test, @testset @testset "test rooted directed graphs" begin - g = NamedDiGraph([1, 2, 3]) - @test !is_rooted(g) - add_edge!(g, 1, 2) - add_edge!(g, 1, 3) - @test is_rooted(g) - @test root_vertex(g) == 1 - @test is_binary_arborescence(g) - add_vertex!(g, 4) - add_edge!(g, 1, 4) - @test !is_binary_arborescence(g) + g = NamedDiGraph([1, 2, 3]) + @test !is_rooted(g) + add_edge!(g, 1, 2) + add_edge!(g, 1, 3) + @test is_rooted(g) + @test root_vertex(g) == 1 + @test is_binary_arborescence(g) + add_vertex!(g, 4) + add_edge!(g, 1, 4) + @test !is_binary_arborescence(g) end end diff --git a/test/test_additensornetworks.jl b/test/test_additensornetworks.jl index 4c2a8cc4..d8f02ceb 100644 --- a/test/test_additensornetworks.jl +++ b/test/test_additensornetworks.jl @@ -9,49 +9,49 @@ using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "add_itensornetworks" begin - g = named_grid((2, 2)) - s = siteinds("S=1/2", g) - ψ1 = ITensorNetwork(v -> "↑", s) - ψ2 = ITensorNetwork(v -> "↓", s) + g = named_grid((2, 2)) + s = siteinds("S=1/2", g) + ψ1 = ITensorNetwork(v -> "↑", s) + ψ2 = ITensorNetwork(v -> "↓", s) - ψ_GHZ = ψ1 + ψ2 + ψ_GHZ = ψ1 + ψ2 - v = (2, 2) - Oψ_GHZ = copy(ψ_GHZ) - Oψ_GHZ[v] = apply(op("Sz", s[v]), Oψ_GHZ[v]) + v = (2, 2) + Oψ_GHZ = copy(ψ_GHZ) + Oψ_GHZ[v] = apply(op("Sz", s[v]), Oψ_GHZ[v]) - ψψ_GHZ = inner_network(ψ_GHZ, ψ_GHZ) - ψOψ_GHZ = inner_network(ψ_GHZ, Oψ_GHZ) + ψψ_GHZ = inner_network(ψ_GHZ, ψ_GHZ) + ψOψ_GHZ = inner_network(ψ_GHZ, Oψ_GHZ) - @test scalar(ψOψ_GHZ) / scalar(ψψ_GHZ) == 0.0 + @test scalar(ψOψ_GHZ) / scalar(ψψ_GHZ) == 0.0 - χ = 3 - s1 = siteinds("S=1/2", g) - s2 = copy(s1) - rem_edge!(s2, NamedEdge((1, 1) => (1, 2))) + χ = 3 + s1 = siteinds("S=1/2", g) + s2 = copy(s1) + rem_edge!(s2, NamedEdge((1, 1) => (1, 2))) - v = rand(vertices(g)) - rng = StableRNG(1234) - ψ1 = random_tensornetwork(rng, s1; link_space=χ) - ψ2 = random_tensornetwork(rng, s2; link_space=χ) + v = rand(vertices(g)) + rng = StableRNG(1234) + ψ1 = random_tensornetwork(rng, s1; link_space = χ) + ψ2 = random_tensornetwork(rng, s2; link_space = χ) - ψ12 = ψ1 + ψ2 + ψ12 = ψ1 + ψ2 - Oψ12 = copy(ψ12) - Oψ12[v] = apply(op("Sz", s1[v]), Oψ12[v]) + Oψ12 = copy(ψ12) + Oψ12[v] = apply(op("Sz", s1[v]), Oψ12[v]) - Oψ1 = copy(ψ1) - Oψ1[v] = apply(op("Sz", s1[v]), Oψ1[v]) + Oψ1 = copy(ψ1) + Oψ1[v] = apply(op("Sz", s1[v]), Oψ1[v]) - Oψ2 = copy(ψ2) - Oψ2[v] = apply(op("Sz", s2[v]), Oψ2[v]) + Oψ2 = copy(ψ2) + Oψ2[v] = apply(op("Sz", s2[v]), Oψ2[v]) - alg = "exact" - expec_method1 = - (inner(ψ1, Oψ1; alg) + inner(ψ2, Oψ2; alg) + 2 * inner(ψ1, Oψ2; alg)) / - (norm_sqr(ψ1; alg) + norm_sqr(ψ2; alg) + 2 * inner(ψ1, ψ2; alg)) - expec_method2 = inner(ψ12, Oψ12; alg) / norm_sqr(ψ12; alg) + alg = "exact" + expec_method1 = + (inner(ψ1, Oψ1; alg) + inner(ψ2, Oψ2; alg) + 2 * inner(ψ1, Oψ2; alg)) / + (norm_sqr(ψ1; alg) + norm_sqr(ψ2; alg) + 2 * inner(ψ1, ψ2; alg)) + expec_method2 = inner(ψ12, Oψ12; alg) / norm_sqr(ψ12; alg) - @test expec_method1 ≈ expec_method2 + @test expec_method1 ≈ expec_method2 end end diff --git a/test/test_apply.jl b/test/test_apply.jl index d7f0d85e..f732ce64 100644 --- a/test/test_apply.jl +++ b/test/test_apply.jl @@ -2,15 +2,15 @@ using Compat: Compat using Graphs: vertices using ITensorNetworks: - BeliefPropagationCache, - ITensorNetwork, - VidalITensorNetwork, - apply, - environment, - norm_sqr_network, - random_tensornetwork, - siteinds, - update + BeliefPropagationCache, + ITensorNetwork, + VidalITensorNetwork, + apply, + environment, + norm_sqr_network, + random_tensornetwork, + siteinds, + update using ITensors: ITensors, ITensor, Algorithm, inner, op using NamedGraphs.NamedGraphGenerators: named_grid using NamedGraphs.PartitionedGraphs: PartitionVertex @@ -19,70 +19,70 @@ using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "apply" begin - g_dims = (2, 2) - n = prod(g_dims) - g = named_grid(g_dims) - s = siteinds("S=1/2", g) - χ = 2 - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, s; link_space=χ) - v1, v2 = (2, 2), (1, 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, [(v1, "bra"), (v1, "ket"), (v2, "bra"), (v2, "ket")]) - ψv = VidalITensorNetwork(ψ; cache_update_kwargs=(; maxiter=20)) - #This grouping will correspond to calculating the environments exactly (each column of the grid is a partition) - bp_cache = BeliefPropagationCache(ψψ, group(v -> v[1][1], vertices(ψψ))) - bp_cache = update(bp_cache; maxiter=20) - envsGBP = environment(bp_cache, [(v1, "bra"), (v1, "ket"), (v2, "bra"), (v2, "ket")]) - inner_alg = "exact" - ngates = 5 - truncerr = 0.0 - singular_values = ITensor() - function callback(; singular_values, truncation_error) - truncerr = truncation_error - singular_values = singular_values - end - for i in 1:ngates - o = op("RandomUnitary", s[v1]..., s[v2]...) - ψOexact = apply(o, ψ; cutoff=nothing) - ψOSBP = apply( - o, - ψ; - envs=envsSBP, - maxdim=χ, - normalize=true, - print_fidelity_loss=true, - envisposdef=true, - callback, - ) - ψOv = apply(o, ψv; maxdim=χ, normalize=true) - ψOVidal_symm = ITensorNetwork(ψOv) - ψOGBP = apply( - o, - ψ; - envs=envsGBP, - maxdim=χ, - normalize=true, - print_fidelity_loss=true, - envisposdef=true, - ) - fSBP = - inner(ψOSBP, ψOexact; alg=inner_alg) / - sqrt(inner(ψOexact, ψOexact; alg=inner_alg) * inner(ψOSBP, ψOSBP; alg=inner_alg)) - fVidal = - inner(ψOVidal_symm, ψOexact; alg=inner_alg) / sqrt( - inner(ψOexact, ψOexact; alg=inner_alg) * - inner(ψOVidal_symm, ψOVidal_symm; alg=inner_alg), - ) - fGBP = - inner(ψOGBP, ψOexact; alg=inner_alg) / - sqrt(inner(ψOexact, ψOexact; alg=inner_alg) * inner(ψOGBP, ψOGBP; alg=inner_alg)) - @test !iszero(truncerr) - @test real(fGBP * conj(fGBP)) >= real(fSBP * conj(fSBP)) - @test isapprox(real(fSBP * conj(fSBP)), real(fVidal * conj(fVidal)); atol=1e-3) - end + g_dims = (2, 2) + n = prod(g_dims) + g = named_grid(g_dims) + s = siteinds("S=1/2", g) + χ = 2 + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = χ) + v1, v2 = (2, 2), (1, 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, [(v1, "bra"), (v1, "ket"), (v2, "bra"), (v2, "ket")]) + ψv = VidalITensorNetwork(ψ; cache_update_kwargs = (; maxiter = 20)) + #This grouping will correspond to calculating the environments exactly (each column of the grid is a partition) + bp_cache = BeliefPropagationCache(ψψ, group(v -> v[1][1], vertices(ψψ))) + bp_cache = update(bp_cache; maxiter = 20) + envsGBP = environment(bp_cache, [(v1, "bra"), (v1, "ket"), (v2, "bra"), (v2, "ket")]) + inner_alg = "exact" + ngates = 5 + truncerr = 0.0 + singular_values = ITensor() + function callback(; singular_values, truncation_error) + truncerr = truncation_error + singular_values = singular_values + end + for i in 1:ngates + o = op("RandomUnitary", s[v1]..., s[v2]...) + ψOexact = apply(o, ψ; cutoff = nothing) + ψOSBP = apply( + o, + ψ; + envs = envsSBP, + maxdim = χ, + normalize = true, + print_fidelity_loss = true, + envisposdef = true, + callback, + ) + ψOv = apply(o, ψv; maxdim = χ, normalize = true) + ψOVidal_symm = ITensorNetwork(ψOv) + ψOGBP = apply( + o, + ψ; + envs = envsGBP, + maxdim = χ, + normalize = true, + print_fidelity_loss = true, + envisposdef = true, + ) + fSBP = + inner(ψOSBP, ψOexact; alg = inner_alg) / + sqrt(inner(ψOexact, ψOexact; alg = inner_alg) * inner(ψOSBP, ψOSBP; alg = inner_alg)) + fVidal = + inner(ψOVidal_symm, ψOexact; alg = inner_alg) / sqrt( + inner(ψOexact, ψOexact; alg = inner_alg) * + inner(ψOVidal_symm, ψOVidal_symm; alg = inner_alg), + ) + fGBP = + inner(ψOGBP, ψOexact; alg = inner_alg) / + sqrt(inner(ψOexact, ψOexact; alg = inner_alg) * inner(ψOGBP, ψOGBP; alg = inner_alg)) + @test !iszero(truncerr) + @test real(fGBP * conj(fGBP)) >= real(fSBP * conj(fSBP)) + @test isapprox(real(fSBP * conj(fSBP)), real(fVidal * conj(fVidal)); atol = 1.0e-3) + end end end diff --git a/test/test_aqua.jl b/test/test_aqua.jl index 59c6ef22..be04599b 100644 --- a/test/test_aqua.jl +++ b/test/test_aqua.jl @@ -3,5 +3,5 @@ using Aqua: Aqua using Test: @testset @testset "Code quality (Aqua.jl)" begin - # Aqua.test_all(ITensorNetworks) + # Aqua.test_all(ITensorNetworks) end diff --git a/test/test_belief_propagation.jl b/test/test_belief_propagation.jl index 3aa55ed4..dbc60d02 100644 --- a/test/test_belief_propagation.jl +++ b/test/test_belief_propagation.jl @@ -3,30 +3,30 @@ using Compat: Compat using Graphs: vertices # Trigger package extension. using ITensorNetworks: - ITensorNetworks, - BeliefPropagationCache, - ⊗, - @preserve_graph, - combine_linkinds, - contract, - contraction_sequence, - eachtensor, - environment, - inner_network, - linkinds_combiners, - message, - partitioned_tensornetwork, - random_tensornetwork, - scalar, - siteinds, - split_index, - tensornetwork, - update, - update_factor, - updated_message, - message_diff + ITensorNetworks, + BeliefPropagationCache, + ⊗, + @preserve_graph, + combine_linkinds, + contract, + contraction_sequence, + eachtensor, + environment, + inner_network, + linkinds_combiners, + message, + partitioned_tensornetwork, + random_tensornetwork, + scalar, + siteinds, + split_index, + tensornetwork, + update, + update_factor, + updated_message, + message_diff using ITensors: - ITensors, ITensor, Algorithm, combiner, dag, inds, inner, op, prime, random_itensor + ITensors, ITensor, Algorithm, combiner, dag, inds, inner, op, prime, random_itensor using ITensorNetworks.ModelNetworks: ModelNetworks using ITensors.NDTensors: array using LinearAlgebra: eigvals, tr @@ -39,67 +39,67 @@ using TensorOperations: TensorOperations using Test: @test, @testset @testset "belief_propagation (eltype=$elt)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} -) - begin - ITensors.disable_warn_order() - g = named_grid((3, 3)) - s = siteinds("S=1/2", g) - χ = 2 - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, elt, s; link_space=χ) - ψψ = ψ ⊗ prime(dag(ψ); sites=[]) - bpc = BeliefPropagationCache(ψψ, group(v -> first(v), vertices(ψψ))) - - #Test updating the tensors in the cache - vket, vbra = ((1, 1), 1), ((1, 1), 2) - A = bpc[vket] - new_A = random_itensor(elt, inds(A)) - new_A_dag = ITensors.replaceind( - dag(prime(new_A)), only(s[first(vket)])', only(s[first(vket)]) + Float32, Float64, Complex{Float32}, Complex{Float64}, ) - @preserve_graph bpc[vket] = new_A - @preserve_graph bpc[vbra] = new_A_dag - @test bpc[vket] == new_A - @test bpc[vbra] == new_A_dag + begin + ITensors.disable_warn_order() + g = named_grid((3, 3)) + s = siteinds("S=1/2", g) + χ = 2 + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, elt, s; link_space = χ) + ψψ = ψ ⊗ prime(dag(ψ); sites = []) + bpc = BeliefPropagationCache(ψψ, group(v -> first(v), vertices(ψψ))) - bpc = update(bpc; alg="bp", maxiter=25, tol=eps(real(elt))) - #Test messages are converged - for pe in partitionedges(bpc) - @test message_diff(updated_message(bpc, pe), message(bpc, pe)) < 10 * eps(real(elt)) - @test eltype(only(message(bpc, pe))) == elt - end - #Test updating the underlying tensornetwork in the cache - v = first(vertices(ψψ)) - rng = StableRNG(1234) - new_tensor = random_itensor(rng, inds(ψψ[v])) - bpc_updated = update_factor(bpc, v, new_tensor) - ψψ_updated = tensornetwork(bpc_updated) - @test ψψ_updated[v] == new_tensor + #Test updating the tensors in the cache + vket, vbra = ((1, 1), 1), ((1, 1), 2) + A = bpc[vket] + new_A = random_itensor(elt, inds(A)) + new_A_dag = ITensors.replaceind( + dag(prime(new_A)), only(s[first(vket)])', only(s[first(vket)]) + ) + @preserve_graph bpc[vket] = new_A + @preserve_graph bpc[vbra] = new_A_dag + @test bpc[vket] == new_A + @test bpc[vbra] == new_A_dag - #Test forming a two-site RDM. Check it has the correct size, trace 1 and is PSD - vs = [(2, 2), (2, 3)] + bpc = update(bpc; alg = "bp", maxiter = 25, tol = eps(real(elt))) + #Test messages are converged + for pe in partitionedges(bpc) + @test message_diff(updated_message(bpc, pe), message(bpc, pe)) < 10 * eps(real(elt)) + @test eltype(only(message(bpc, pe))) == elt + end + #Test updating the underlying tensornetwork in the cache + v = first(vertices(ψψ)) + rng = StableRNG(1234) + new_tensor = random_itensor(rng, inds(ψψ[v])) + bpc_updated = update_factor(bpc, v, new_tensor) + ψψ_updated = tensornetwork(bpc_updated) + @test ψψ_updated[v] == new_tensor - ψψsplit = split_index(ψψ, NamedEdge.([(v, 1) => (v, 2) for v in vs])) - env_tensors = environment(bpc, [(v, 2) for v in vs]) - rdm = contract(vcat(env_tensors, ITensor[ψψsplit[vp] for vp in [(v, 2) for v in vs]])) + #Test forming a two-site RDM. Check it has the correct size, trace 1 and is PSD + vs = [(2, 2), (2, 3)] - rdm = array((rdm * combiner(inds(rdm; plev=0)...)) * combiner(inds(rdm; plev=1)...)) - rdm /= tr(rdm) + ψψsplit = split_index(ψψ, NamedEdge.([(v, 1) => (v, 2) for v in vs])) + env_tensors = environment(bpc, [(v, 2) for v in vs]) + rdm = contract(vcat(env_tensors, ITensor[ψψsplit[vp] for vp in [(v, 2) for v in vs]])) - eigs = eigvals(rdm) - @test size(rdm) == (2^length(vs), 2^length(vs)) + rdm = array((rdm * combiner(inds(rdm; plev = 0)...)) * combiner(inds(rdm; plev = 1)...)) + rdm /= tr(rdm) - @test all(eig -> abs(imag(eig)) <= eps(real(elt)), eigs) - @test all(eig -> real(eig) >= -eps(real(elt)), eigs) + eigs = eigvals(rdm) + @test size(rdm) == (2^length(vs), 2^length(vs)) - #Test edge case of network which evalutes to 0 - χ = 2 - g = named_grid((3, 1)) - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, elt, g; link_space=χ) - ψ[(1, 1)] = 0 * ψ[(1, 1)] - @test iszero(scalar(ψ; alg="bp")) - end + @test all(eig -> abs(imag(eig)) <= eps(real(elt)), eigs) + @test all(eig -> real(eig) >= -eps(real(elt)), eigs) + + #Test edge case of network which evalutes to 0 + χ = 2 + g = named_grid((3, 1)) + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, elt, g; link_space = χ) + ψ[(1, 1)] = 0 * ψ[(1, 1)] + @test iszero(scalar(ψ; alg = "bp")) + end end end diff --git a/test/test_binary_tree_partition.jl b/test/test_binary_tree_partition.jl index f44536e7..16fd35d5 100644 --- a/test/test_binary_tree_partition.jl +++ b/test/test_binary_tree_partition.jl @@ -5,49 +5,49 @@ using Graphs: add_vertex!, vertices using GraphsFlows: GraphsFlows using ITensors: Index, ITensor, contract, noncommoninds, random_itensor using ITensorNetworks: - _DensityMartrixAlgGraph, - _contract_deltas_ignore_leaf_partitions, - _mps_partition_inds_order, - _mincut_partitions, - _partition, - _rem_vertex!, - IndsNetwork, - ITensorNetwork, - binary_tree_structure, - eachtensor, - path_graph_structure, - random_tensornetwork + _DensityMartrixAlgGraph, + _contract_deltas_ignore_leaf_partitions, + _mps_partition_inds_order, + _mincut_partitions, + _partition, + _rem_vertex!, + IndsNetwork, + ITensorNetwork, + binary_tree_structure, + eachtensor, + path_graph_structure, + random_tensornetwork using NamedGraphs: NamedEdge, NamedGraph using NamedGraphs.NamedGraphGenerators: named_grid using NamedGraphs.GraphsExtensions: - is_binary_arborescence, post_order_dfs_vertices, root_vertex + is_binary_arborescence, post_order_dfs_vertices, root_vertex using OMEinsumContractionOrders: OMEinsumContractionOrders using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "test _binary_tree_partition_inds of a 2D network" begin - N = (3, 3, 3) - linkdim = 2 - rng = StableRNG(1234) - network = random_tensornetwork(rng, IndsNetwork(named_grid(N)); link_space=linkdim) - tn = Array{ITensor,length(N)}(undef, N...) - for v in vertices(network) - tn[v...] = network[v...] - end - tn = ITensorNetwork(vec(tn[:, :, 1])) - for out in [binary_tree_structure(tn), path_graph_structure(tn)] - @test out isa DataGraph - @test is_binary_arborescence(out) - @test length(vertex_data(out).values) == 9 - end + N = (3, 3, 3) + linkdim = 2 + rng = StableRNG(1234) + network = random_tensornetwork(rng, IndsNetwork(named_grid(N)); link_space = linkdim) + tn = Array{ITensor, length(N)}(undef, N...) + for v in vertices(network) + tn[v...] = network[v...] + end + tn = ITensorNetwork(vec(tn[:, :, 1])) + for out in [binary_tree_structure(tn), path_graph_structure(tn)] + @test out isa DataGraph + @test is_binary_arborescence(out) + @test length(vertex_data(out).values) == 9 + end end @testset "test partition with mincut_recursive_bisection alg of disconnected tn" begin - inds = [Index(2, "$i") for i in 1:5] - tn = ITensorNetwork([random_itensor(i) for i in inds]) - par = _partition(tn, binary_tree_structure(tn); alg="mincut_recursive_bisection") - network = mapreduce(v -> collect(eachtensor(par[v])), vcat, vertices(par)) - @test isapprox(contract(tn), contract(network)) + inds = [Index(2, "$i") for i in 1:5] + tn = ITensorNetwork([random_itensor(i) for i in inds]) + par = _partition(tn, binary_tree_structure(tn); alg = "mincut_recursive_bisection") + network = mapreduce(v -> collect(eachtensor(par[v])), vcat, vertices(par)) + @test isapprox(contract(tn), contract(network)) end end diff --git a/test/test_contract_deltas.jl b/test/test_contract_deltas.jl index 93c8d768..ee1b5305 100644 --- a/test/test_contract_deltas.jl +++ b/test/test_contract_deltas.jl @@ -4,62 +4,62 @@ using Graphs: dfs_tree, nv, vertices using GraphsFlows: GraphsFlows using ITensors: Index, ITensor, delta, noncommoninds, random_itensor using ITensorNetworks: - IndsNetwork, - ITensorNetwork, - _contract_deltas, - _contract_deltas_ignore_leaf_partitions, - _noncommoninds, - _partition, - binary_tree_structure, - eachtensor, - flatten_siteinds, - path_graph_structure, - random_tensornetwork + IndsNetwork, + ITensorNetwork, + _contract_deltas, + _contract_deltas_ignore_leaf_partitions, + _noncommoninds, + _partition, + binary_tree_structure, + eachtensor, + flatten_siteinds, + path_graph_structure, + random_tensornetwork using NamedGraphs.GraphsExtensions: leaf_vertices, root_vertex using NamedGraphs.NamedGraphGenerators: named_grid using StableRNGs: StableRNG using Test: @test, @testset @testset "test _contract_deltas with no deltas" begin - i = Index(2, "i") - t = random_itensor(i) - tn = _contract_deltas(ITensorNetwork([t])) - @test tn[1] == t + i = Index(2, "i") + t = random_itensor(i) + tn = _contract_deltas(ITensorNetwork([t])) + @test tn[1] == t end @testset "test _contract_deltas over ITensorNetwork" begin - is = [Index(2, string(i)) for i in 1:6] - a = ITensor(is[1], is[2]) - b = ITensor(is[2], is[3]) - delta1 = delta(is[3], is[4]) - delta2 = delta(is[5], is[6]) - tn = ITensorNetwork([a, b, delta1, delta2]) - tn2 = _contract_deltas(tn) - @test nv(tn2) == 3 - @test issetequal(flatten_siteinds(tn), flatten_siteinds(tn2)) + is = [Index(2, string(i)) for i in 1:6] + a = ITensor(is[1], is[2]) + b = ITensor(is[2], is[3]) + delta1 = delta(is[3], is[4]) + delta2 = delta(is[5], is[6]) + tn = ITensorNetwork([a, b, delta1, delta2]) + tn2 = _contract_deltas(tn) + @test nv(tn2) == 3 + @test issetequal(flatten_siteinds(tn), flatten_siteinds(tn2)) end @testset "test _contract_deltas over partition" begin - N = (3, 3, 3) - linkdim = 2 - rng = StableRNG(1234) - network = random_tensornetwork(rng, IndsNetwork(named_grid(N)); link_space=linkdim) - tn = Array{ITensor,length(N)}(undef, N...) - for v in vertices(network) - tn[v...] = network[v...] - end - tn = ITensorNetwork(vec(tn[:, :, 1])) - for inds_tree in [binary_tree_structure(tn), path_graph_structure(tn)] - par = _partition(tn, inds_tree; alg="mincut_recursive_bisection") - root = root_vertex(inds_tree) - par_contract_deltas = _contract_deltas_ignore_leaf_partitions(par; root=root) - @test Set(_noncommoninds(par)) == Set(_noncommoninds(par_contract_deltas)) - leaves = leaf_vertices(dfs_tree(par_contract_deltas, root)) - nonleaf_vertices = setdiff(vertices(par_contract_deltas), leaves) - nvs = sum([nv(par_contract_deltas[v]) for v in nonleaf_vertices]) - # all delta tensors in nonleaf vertives should be contracted, so the - # remaining tensors are all non-delta tensors - @test nvs == 9 - end + N = (3, 3, 3) + linkdim = 2 + rng = StableRNG(1234) + network = random_tensornetwork(rng, IndsNetwork(named_grid(N)); link_space = linkdim) + tn = Array{ITensor, length(N)}(undef, N...) + for v in vertices(network) + tn[v...] = network[v...] + end + tn = ITensorNetwork(vec(tn[:, :, 1])) + for inds_tree in [binary_tree_structure(tn), path_graph_structure(tn)] + par = _partition(tn, inds_tree; alg = "mincut_recursive_bisection") + root = root_vertex(inds_tree) + par_contract_deltas = _contract_deltas_ignore_leaf_partitions(par; root = root) + @test Set(_noncommoninds(par)) == Set(_noncommoninds(par_contract_deltas)) + leaves = leaf_vertices(dfs_tree(par_contract_deltas, root)) + nonleaf_vertices = setdiff(vertices(par_contract_deltas), leaves) + nvs = sum([nv(par_contract_deltas[v]) for v in nonleaf_vertices]) + # all delta tensors in nonleaf vertives should be contracted, so the + # remaining tensors are all non-delta tensors + @test nvs == 9 + end end end diff --git a/test/test_contraction_sequence.jl b/test/test_contraction_sequence.jl index 0fad02ba..baaaeeab 100644 --- a/test/test_contraction_sequence.jl +++ b/test/test_contraction_sequence.jl @@ -1,7 +1,7 @@ @eval module $(gensym()) using EinExprs: Exhaustive, Greedy using ITensorNetworks: - contraction_sequence, norm_sqr_network, random_tensornetwork, siteinds + contraction_sequence, norm_sqr_network, random_tensornetwork, siteinds using ITensors: ITensors, contract using NamedGraphs.NamedGraphGenerators: named_grid using OMEinsumContractionOrders: OMEinsumContractionOrders @@ -9,51 +9,51 @@ using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "contraction_sequence" begin - ITensors.@disable_warn_order begin - dims = (2, 3) - g = named_grid(dims) - s = siteinds("S=1/2", g) - χ = 10 - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, s; link_space=χ) - tn = norm_sqr_network(ψ) - seq_optimal = contraction_sequence(tn; alg="optimal") - res_optimal = contract(tn; sequence=seq_optimal)[] - seq_greedy = contraction_sequence(tn; alg="greedy") - res_greedy = contract(tn; sequence=seq_greedy)[] - seq_tree_sa = contraction_sequence(tn; alg="tree_sa") - res_tree_sa = contract(tn; sequence=seq_tree_sa)[] - seq_sa_bipartite = contraction_sequence(tn; alg="sa_bipartite") - res_sa_bipartite = contract(tn; sequence=seq_sa_bipartite)[] - seq_einexprs_exhaustive = contraction_sequence( - tn; alg="einexpr", optimizer=Exhaustive() - ) - res_einexprs_exhaustive = contract(tn; sequence=seq_einexprs_exhaustive)[] - seq_einexprs_greedy = contraction_sequence(tn; alg="einexpr", optimizer=Greedy()) - res_einexprs_greedy = contract(tn; sequence=seq_einexprs_exhaustive)[] - @test res_greedy ≈ res_optimal - @test res_tree_sa ≈ res_optimal - @test res_sa_bipartite ≈ res_optimal - @test res_einexprs_exhaustive ≈ res_optimal - @test res_einexprs_greedy ≈ res_optimal + ITensors.@disable_warn_order begin + dims = (2, 3) + g = named_grid(dims) + s = siteinds("S=1/2", g) + χ = 10 + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = χ) + tn = norm_sqr_network(ψ) + seq_optimal = contraction_sequence(tn; alg = "optimal") + res_optimal = contract(tn; sequence = seq_optimal)[] + seq_greedy = contraction_sequence(tn; alg = "greedy") + res_greedy = contract(tn; sequence = seq_greedy)[] + seq_tree_sa = contraction_sequence(tn; alg = "tree_sa") + res_tree_sa = contract(tn; sequence = seq_tree_sa)[] + seq_sa_bipartite = contraction_sequence(tn; alg = "sa_bipartite") + res_sa_bipartite = contract(tn; sequence = seq_sa_bipartite)[] + seq_einexprs_exhaustive = contraction_sequence( + tn; alg = "einexpr", optimizer = Exhaustive() + ) + res_einexprs_exhaustive = contract(tn; sequence = seq_einexprs_exhaustive)[] + seq_einexprs_greedy = contraction_sequence(tn; alg = "einexpr", optimizer = Greedy()) + res_einexprs_greedy = contract(tn; sequence = seq_einexprs_exhaustive)[] + @test res_greedy ≈ res_optimal + @test res_tree_sa ≈ res_optimal + @test res_sa_bipartite ≈ res_optimal + @test res_einexprs_exhaustive ≈ res_optimal + @test res_einexprs_greedy ≈ res_optimal - if !Sys.iswindows() - # KaHyPar doesn't work on Windows - # https://github.com/kahypar/KaHyPar.jl/issues/9 - using Pkg - Pkg.add("KaHyPar"; io=devnull) - using KaHyPar - seq_kahypar_bipartite = contraction_sequence( - tn; alg="kahypar_bipartite", sc_target=200 - ) - Pkg.rm("KaHyPar"; io=devnull) - res_kahypar_bipartite = contract(tn; sequence=seq_kahypar_bipartite)[] - @test res_optimal ≈ res_kahypar_bipartite - #These tests were leading to CI issues that need to be investigated - # seq_einexprs_kahypar = contraction_sequence(tn; alg="einexpr", optimizer=HyPar()) - # res_einexprs_kahypar = contract(tn; sequence=seq_einexprs_kahypar)[] - # @test res_einexprs_kahypar ≈ res_optimal + if !Sys.iswindows() + # KaHyPar doesn't work on Windows + # https://github.com/kahypar/KaHyPar.jl/issues/9 + using Pkg + Pkg.add("KaHyPar"; io = devnull) + using KaHyPar + seq_kahypar_bipartite = contraction_sequence( + tn; alg = "kahypar_bipartite", sc_target = 200 + ) + Pkg.rm("KaHyPar"; io = devnull) + res_kahypar_bipartite = contract(tn; sequence = seq_kahypar_bipartite)[] + @test res_optimal ≈ res_kahypar_bipartite + #These tests were leading to CI issues that need to be investigated + # seq_einexprs_kahypar = contraction_sequence(tn; alg="einexpr", optimizer=HyPar()) + # res_einexprs_kahypar = contract(tn; sequence=seq_einexprs_kahypar)[] + # @test res_einexprs_kahypar ≈ res_optimal + end end - end end end diff --git a/test/test_examples.jl b/test/test_examples.jl index d3432d5b..f4fbd410 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -4,9 +4,9 @@ using Suppressor: @suppress using Test: @testset @testset "Test examples" begin - example_files = ["README.jl"] - @testset "Test $example_file" for example_file in example_files - @suppress include(joinpath(pkgdir(ITensorNetworks), "examples", example_file)) - end + example_files = ["README.jl"] + @testset "Test $example_file" for example_file in example_files + @suppress include(joinpath(pkgdir(ITensorNetworks), "examples", example_file)) + end end end diff --git a/test/test_expect.jl b/test/test_expect.jl index 92579434..a3e87107 100644 --- a/test/test_expect.jl +++ b/test/test_expect.jl @@ -3,54 +3,54 @@ using Graphs: SimpleGraph, uniform_tree using NamedGraphs: NamedGraph, vertices using NamedGraphs.NamedGraphGenerators: named_grid using ITensorNetworks: - BeliefPropagationCache, - ITensorNetwork, - expect, - random_tensornetwork, - siteinds, - original_state_vertex + BeliefPropagationCache, + ITensorNetwork, + expect, + random_tensornetwork, + siteinds, + original_state_vertex using SplitApplyCombine: group using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "Test Expect" begin - #Test on a tree - L, χ = 4, 2 - g = NamedGraph(SimpleGraph(uniform_tree(L))) - s = siteinds("S=1/2", g) - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, s; link_space=χ) - sz_bp = expect(ψ, "Sz"; alg="bp") - sz_exact = expect(ψ, "Sz"; alg="exact") - @test sz_bp ≈ sz_exact + #Test on a tree + L, χ = 4, 2 + g = NamedGraph(SimpleGraph(uniform_tree(L))) + s = siteinds("S=1/2", g) + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = χ) + sz_bp = expect(ψ, "Sz"; alg = "bp") + sz_exact = expect(ψ, "Sz"; alg = "exact") + @test sz_bp ≈ sz_exact - #Test on a grid, group by column to make BP exact - L, χ = 2, 2 - g = named_grid((L, L)) - s = siteinds("S=1/2", g) - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, s; link_space=χ) - quadratic_form_vertices = reduce( - vcat, [[(v, "ket"), (v, "bra"), (v, "operator")] for v in vertices(ψ)] - ) - cache_construction_kwargs = (; - partitioned_vertices=group(v -> first(first(v)), quadratic_form_vertices) - ) - sz_bp = expect( - ψ, "Sz"; alg="bp", cache_construction_kwargs, cache_update_kwargs=(; maxiter=20) - ) - sz_exact = expect(ψ, "Sz"; alg="exact") - @test sz_bp ≈ sz_exact + #Test on a grid, group by column to make BP exact + L, χ = 2, 2 + g = named_grid((L, L)) + s = siteinds("S=1/2", g) + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = χ) + quadratic_form_vertices = reduce( + vcat, [[(v, "ket"), (v, "bra"), (v, "operator")] for v in vertices(ψ)] + ) + cache_construction_kwargs = (; + partitioned_vertices = group(v -> first(first(v)), quadratic_form_vertices), + ) + sz_bp = expect( + ψ, "Sz"; alg = "bp", cache_construction_kwargs, cache_update_kwargs = (; maxiter = 20) + ) + sz_exact = expect(ψ, "Sz"; alg = "exact") + @test sz_bp ≈ sz_exact - #Test with Quantum Numbers, product state so BP should be exact - L, χ = 2, 2 - g = named_grid((L, L)) - s = siteinds("S=1/2", g; conserve_qns=true) + #Test with Quantum Numbers, product state so BP should be exact + L, χ = 2, 2 + g = named_grid((L, L)) + s = siteinds("S=1/2", g; conserve_qns = true) - ψ = ITensorNetwork(v -> isodd(sum(v)) ? "↑" : "↓", s) + ψ = ITensorNetwork(v -> isodd(sum(v)) ? "↑" : "↓", s) - sz_bp = expect(ψ, "Sz"; alg="bp", cache_update_kwargs=(; maxiter=20)) - sz_exact = expect(ψ, "Sz"; alg="exact") - @test sz_bp ≈ sz_exact + sz_bp = expect(ψ, "Sz"; alg = "bp", cache_update_kwargs = (; maxiter = 20)) + sz_exact = expect(ψ, "Sz"; alg = "exact") + @test sz_bp ≈ sz_exact end end diff --git a/test/test_forms.jl b/test/test_forms.jl index 1a267c8e..dc81aa0f 100644 --- a/test/test_forms.jl +++ b/test/test_forms.jl @@ -3,108 +3,108 @@ using DataGraphs: underlying_graph using Graphs: nv using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_grid using ITensorNetworks: - BeliefPropagationCache, - BilinearFormNetwork, - LinearFormNetwork, - QuadraticFormNetwork, - bra_network, - bra_vertex, - dual_index_map, - environment, - flatten_siteinds, - inner, - ket_network, - ket_vertex, - operator_network, - random_tensornetwork, - scalar, - siteinds, - state_vertices, - tensornetwork, - union_all_inds, - update + BeliefPropagationCache, + BilinearFormNetwork, + LinearFormNetwork, + QuadraticFormNetwork, + bra_network, + bra_vertex, + dual_index_map, + environment, + flatten_siteinds, + inner, + ket_network, + ket_vertex, + operator_network, + random_tensornetwork, + scalar, + siteinds, + state_vertices, + tensornetwork, + union_all_inds, + update using ITensors: Index, contract, dag, inds, prime, random_itensor, sim using LinearAlgebra: norm using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "FormNetworks" begin - g = named_grid((1, 4)) - s = siteinds("S=1/2", g) - s_operator = union_all_inds(s, prime(s)) - χ, D = 2, 3 - rng = StableRNG(1234) - ψket = random_tensornetwork(rng, s; link_space=χ) - ψbra = random_tensornetwork(rng, s; link_space=χ) - A = random_tensornetwork(rng, s_operator; link_space=D) - - lf = LinearFormNetwork(ψbra, ψket) - @test nv(lf) == nv(ψket) + nv(ψbra) - @test isempty(flatten_siteinds(lf)) - - blf = BilinearFormNetwork(A, ψbra, ψket) - @test nv(blf) == nv(ψket) + nv(ψbra) + nv(A) - @test isempty(flatten_siteinds(blf)) - - @test underlying_graph(ket_network(blf)) == underlying_graph(ψket) - @test underlying_graph(operator_network(blf)) == underlying_graph(A) - @test underlying_graph(bra_network(blf)) == underlying_graph(ψbra) - - lf = LinearFormNetwork(blf) - @test underlying_graph(ket_network(lf)) == underlying_graph(ψket) - - qf = QuadraticFormNetwork(ψket) - @test nv(qf) == 3 * nv(ψket) - @test isempty(flatten_siteinds(qf)) - - qf = QuadraticFormNetwork(A, ψket) - @test nv(qf) == 2 * nv(ψket) + nv(A) - @test isempty(flatten_siteinds(qf)) - - v = (1, 1) - rng = StableRNG(1234) - new_tensor = random_itensor(rng, inds(ψket[v])) - qf_updated = update(qf, v, copy(new_tensor)) - - @test tensornetwork(qf_updated)[bra_vertex(qf_updated, v)] ≈ - dual_index_map(qf_updated)(dag(new_tensor)) - @test tensornetwork(qf_updated)[ket_vertex(qf_updated, v)] ≈ new_tensor - - @test underlying_graph(ket_network(qf)) == underlying_graph(ψket) - @test underlying_graph(operator_network(qf)) == underlying_graph(A) - - ∂qf_∂v = only(environment(qf, state_vertices(qf, [v]); alg="exact")) - @test (∂qf_∂v) * (qf[ket_vertex(qf, v)] * qf[bra_vertex(qf, v)]) ≈ contract(qf) - - ∂qf_∂v_bp = environment(qf, state_vertices(qf, [v]); alg="bp", update_cache=false) - ∂qf_∂v_bp = contract(∂qf_∂v_bp) - ∂qf_∂v_bp /= norm(∂qf_∂v_bp) - ∂qf_∂v /= norm(∂qf_∂v) - @test ∂qf_∂v_bp != ∂qf_∂v - - ∂qf_∂v_bp = environment(qf, state_vertices(qf, [v]); alg="bp", update_cache=true) - ∂qf_∂v_bp = contract(∂qf_∂v_bp) - ∂qf_∂v_bp /= norm(∂qf_∂v_bp) - @test ∂qf_∂v_bp ≈ ∂qf_∂v - - #Test having non-uniform number of site indices per vertex - g = named_comb_tree((3, 3)) - s = siteinds("S=1/2", g) - s = union_all_inds(s, sim(s)) - s[(1, 1)] = Index[] - s[(3, 3)] = Index[first(s[(3, 3)])] - χ = 2 - rng = StableRNG(1234) - ψket = random_tensornetwork(rng, ComplexF64, s; link_space=χ) - ψbra = random_tensornetwork(rng, ComplexF64, s; link_space=χ) - - blf = BilinearFormNetwork(ψbra, ψket) - @test scalar(blf; alg="exact") ≈ inner(ψbra, ψket; alg="exact") - - lf = LinearFormNetwork(ψbra, ψket) - @test scalar(lf; alg="exact") ≈ inner(ψbra, ψket; alg="exact") - - qf = QuadraticFormNetwork(ψket) - @test scalar(qf; alg="exact") ≈ inner(ψket, ψket; alg="exact") + g = named_grid((1, 4)) + s = siteinds("S=1/2", g) + s_operator = union_all_inds(s, prime(s)) + χ, D = 2, 3 + rng = StableRNG(1234) + ψket = random_tensornetwork(rng, s; link_space = χ) + ψbra = random_tensornetwork(rng, s; link_space = χ) + A = random_tensornetwork(rng, s_operator; link_space = D) + + lf = LinearFormNetwork(ψbra, ψket) + @test nv(lf) == nv(ψket) + nv(ψbra) + @test isempty(flatten_siteinds(lf)) + + blf = BilinearFormNetwork(A, ψbra, ψket) + @test nv(blf) == nv(ψket) + nv(ψbra) + nv(A) + @test isempty(flatten_siteinds(blf)) + + @test underlying_graph(ket_network(blf)) == underlying_graph(ψket) + @test underlying_graph(operator_network(blf)) == underlying_graph(A) + @test underlying_graph(bra_network(blf)) == underlying_graph(ψbra) + + lf = LinearFormNetwork(blf) + @test underlying_graph(ket_network(lf)) == underlying_graph(ψket) + + qf = QuadraticFormNetwork(ψket) + @test nv(qf) == 3 * nv(ψket) + @test isempty(flatten_siteinds(qf)) + + qf = QuadraticFormNetwork(A, ψket) + @test nv(qf) == 2 * nv(ψket) + nv(A) + @test isempty(flatten_siteinds(qf)) + + v = (1, 1) + rng = StableRNG(1234) + new_tensor = random_itensor(rng, inds(ψket[v])) + qf_updated = update(qf, v, copy(new_tensor)) + + @test tensornetwork(qf_updated)[bra_vertex(qf_updated, v)] ≈ + dual_index_map(qf_updated)(dag(new_tensor)) + @test tensornetwork(qf_updated)[ket_vertex(qf_updated, v)] ≈ new_tensor + + @test underlying_graph(ket_network(qf)) == underlying_graph(ψket) + @test underlying_graph(operator_network(qf)) == underlying_graph(A) + + ∂qf_∂v = only(environment(qf, state_vertices(qf, [v]); alg = "exact")) + @test (∂qf_∂v) * (qf[ket_vertex(qf, v)] * qf[bra_vertex(qf, v)]) ≈ contract(qf) + + ∂qf_∂v_bp = environment(qf, state_vertices(qf, [v]); alg = "bp", update_cache = false) + ∂qf_∂v_bp = contract(∂qf_∂v_bp) + ∂qf_∂v_bp /= norm(∂qf_∂v_bp) + ∂qf_∂v /= norm(∂qf_∂v) + @test ∂qf_∂v_bp != ∂qf_∂v + + ∂qf_∂v_bp = environment(qf, state_vertices(qf, [v]); alg = "bp", update_cache = true) + ∂qf_∂v_bp = contract(∂qf_∂v_bp) + ∂qf_∂v_bp /= norm(∂qf_∂v_bp) + @test ∂qf_∂v_bp ≈ ∂qf_∂v + + #Test having non-uniform number of site indices per vertex + g = named_comb_tree((3, 3)) + s = siteinds("S=1/2", g) + s = union_all_inds(s, sim(s)) + s[(1, 1)] = Index[] + s[(3, 3)] = Index[first(s[(3, 3)])] + χ = 2 + rng = StableRNG(1234) + ψket = random_tensornetwork(rng, ComplexF64, s; link_space = χ) + ψbra = random_tensornetwork(rng, ComplexF64, s; link_space = χ) + + blf = BilinearFormNetwork(ψbra, ψket) + @test scalar(blf; alg = "exact") ≈ inner(ψbra, ψket; alg = "exact") + + lf = LinearFormNetwork(ψbra, ψket) + @test scalar(lf; alg = "exact") ≈ inner(ψbra, ψket; alg = "exact") + + qf = QuadraticFormNetwork(ψket) + @test scalar(qf; alg = "exact") ≈ inner(ψket, ψket; alg = "exact") end end diff --git a/test/test_gauging.jl b/test/test_gauging.jl index a9906465..42374942 100644 --- a/test/test_gauging.jl +++ b/test/test_gauging.jl @@ -1,14 +1,14 @@ @eval module $(gensym()) using Compat: Compat using ITensorNetworks: - BeliefPropagationCache, - ITensorNetwork, - VidalITensorNetwork, - gauge_error, - messages, - random_tensornetwork, - siteinds, - update + BeliefPropagationCache, + ITensorNetwork, + VidalITensorNetwork, + gauge_error, + messages, + random_tensornetwork, + siteinds, + update using ITensors: diag_itensor, inds, inner using ITensors.NDTensors: Algorithm, vector using LinearAlgebra: diag @@ -18,35 +18,35 @@ using TensorOperations: TensorOperations using Test: @test, @testset @testset "gauging" begin - n = 3 - dims = (n, n) - g = named_grid(dims) - s = siteinds("S=1/2", g) - χ = 6 + n = 3 + dims = (n, n) + g = named_grid(dims) + s = siteinds("S=1/2", g) + χ = 6 - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, s; link_space=χ) + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = χ) - # Move directly to vidal gauge - ψ_vidal = VidalITensorNetwork( - ψ; cache_update_kwargs=(; alg="bp", maxiter=30, verbose=true) - ) - @test gauge_error(ψ_vidal) < 1e-8 + # Move directly to vidal gauge + ψ_vidal = VidalITensorNetwork( + ψ; cache_update_kwargs = (; alg = "bp", maxiter = 30, verbose = true) + ) + @test gauge_error(ψ_vidal) < 1.0e-8 - # Move to symmetric gauge - cache_ref = Ref{BeliefPropagationCache}() - ψ_symm = ITensorNetwork(ψ_vidal; (cache!)=cache_ref) - bp_cache = cache_ref[] + # Move to symmetric gauge + cache_ref = Ref{BeliefPropagationCache}() + ψ_symm = ITensorNetwork(ψ_vidal; (cache!) = cache_ref) + bp_cache = cache_ref[] - # Test we just did a gauge transform and didn't change the overall network - @test inner(ψ_symm, ψ; alg="exact") / - sqrt(inner(ψ_symm, ψ_symm; alg="exact") * inner(ψ, ψ; alg="exact")) ≈ 1.0 atol = - 1e-8 + # Test we just did a gauge transform and didn't change the overall network + @test inner(ψ_symm, ψ; alg = "exact") / + sqrt(inner(ψ_symm, ψ_symm; alg = "exact") * inner(ψ, ψ; alg = "exact")) ≈ 1.0 atol = + 1.0e-8 - #Test all message tensors are approximately diagonal even when we keep running BP - bp_cache = update(bp_cache; maxiter=10) - for m_e in values(messages(bp_cache)) - @test diag_itensor(vector(diag(only(m_e))), inds(only(m_e))) ≈ only(m_e) atol = 1e-8 - end + #Test all message tensors are approximately diagonal even when we keep running BP + bp_cache = update(bp_cache; maxiter = 10) + for m_e in values(messages(bp_cache)) + @test diag_itensor(vector(diag(only(m_e))), inds(only(m_e))) ≈ only(m_e) atol = 1.0e-8 + end end end diff --git a/test/test_indsnetwork.jl b/test/test_indsnetwork.jl index 21fdb3f8..49cc565c 100644 --- a/test/test_indsnetwork.jl +++ b/test/test_indsnetwork.jl @@ -9,160 +9,160 @@ using NamedGraphs.NamedGraphGenerators: named_comb_tree using StableRNGs: StableRNG using Test: @test, @testset @testset "IndsNetwork constructors" begin - # test on comb tree - dims = (3, 2) - c = named_comb_tree(dims) - ## specify some site and link indices in different ways - # one index per site - rng = StableRNG(1234) - site_dims = [rand(rng, 2:6) for _ in 1:nv(c)] - site_inds = Index.(site_dims) - # multiple indices per site - site_dims_multi = [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:nv(c)] - site_inds_multi = map(x -> Index.(x), site_dims_multi) - # one index per link - link_dims = [rand(rng, 2:6) for _ in 1:ne(c)] - link_inds = Index.(link_dims) - # multiple indices per link - link_dims_multi = [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:ne(c)] - link_inds_multi = map(x -> Index.(x), link_dims_multi) - # TODO: fix ambiguity due to vectors of QNBlocks... - # test constructors - ## empty constructor - is_emtpy = IndsNetwork(c) - @test is_emtpy isa IndsNetwork - @test isempty(vertex_data(is_emtpy)) && isempty(edge_data(is_emtpy)) - ## specify site and/or link spaces uniformly - uniform_dim = rand(rng, 2:6) - uniform_dim_multi = [rand(rng, 2:6) for _ in 1:rand(rng, 2:4)] - # only initialize sites - is_usite = IndsNetwork(c; site_space=uniform_dim) - @test is_usite isa IndsNetwork - @test all(map(x -> dim.(x) == [uniform_dim], vertex_data(is_usite))) - @test isempty(edge_data(is_usite)) - is_umsite = IndsNetwork(c; site_space=uniform_dim_multi) - @test is_umsite isa IndsNetwork - @test all(map(x -> dim.(x) == uniform_dim_multi, vertex_data(is_umsite))) - @test isempty(edge_data(is_umsite)) - # only initialize links - is_ulink = IndsNetwork(c; link_space=uniform_dim) - @test is_ulink isa IndsNetwork - @test all(map(x -> dim.(x) == [uniform_dim], edge_data(is_ulink))) - @test isempty(vertex_data(is_ulink)) - is_umlink = IndsNetwork(c; link_space=uniform_dim_multi) - @test is_umlink isa IndsNetwork - @test all(map(x -> dim.(x) == uniform_dim_multi, edge_data(is_umlink))) - @test isempty(vertex_data(is_umlink)) - # initialize sites and links - is_usite_umlink = IndsNetwork(c; site_space=uniform_dim, link_space=uniform_dim_multi) - @test is_usite_umlink isa IndsNetwork - @test all(map(x -> dim.(x) == [uniform_dim], vertex_data(is_usite_umlink))) - @test all(map(x -> dim.(x) == uniform_dim_multi, edge_data(is_usite_umlink))) + # test on comb tree + dims = (3, 2) + c = named_comb_tree(dims) + ## specify some site and link indices in different ways + # one index per site + rng = StableRNG(1234) + site_dims = [rand(rng, 2:6) for _ in 1:nv(c)] + site_inds = Index.(site_dims) + # multiple indices per site + site_dims_multi = [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:nv(c)] + site_inds_multi = map(x -> Index.(x), site_dims_multi) + # one index per link + link_dims = [rand(rng, 2:6) for _ in 1:ne(c)] + link_inds = Index.(link_dims) + # multiple indices per link + link_dims_multi = [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:ne(c)] + link_inds_multi = map(x -> Index.(x), link_dims_multi) + # TODO: fix ambiguity due to vectors of QNBlocks... + # test constructors + ## empty constructor + is_emtpy = IndsNetwork(c) + @test is_emtpy isa IndsNetwork + @test isempty(vertex_data(is_emtpy)) && isempty(edge_data(is_emtpy)) + ## specify site and/or link spaces uniformly + uniform_dim = rand(rng, 2:6) + uniform_dim_multi = [rand(rng, 2:6) for _ in 1:rand(rng, 2:4)] + # only initialize sites + is_usite = IndsNetwork(c; site_space = uniform_dim) + @test is_usite isa IndsNetwork + @test all(map(x -> dim.(x) == [uniform_dim], vertex_data(is_usite))) + @test isempty(edge_data(is_usite)) + is_umsite = IndsNetwork(c; site_space = uniform_dim_multi) + @test is_umsite isa IndsNetwork + @test all(map(x -> dim.(x) == uniform_dim_multi, vertex_data(is_umsite))) + @test isempty(edge_data(is_umsite)) + # only initialize links + is_ulink = IndsNetwork(c; link_space = uniform_dim) + @test is_ulink isa IndsNetwork + @test all(map(x -> dim.(x) == [uniform_dim], edge_data(is_ulink))) + @test isempty(vertex_data(is_ulink)) + is_umlink = IndsNetwork(c; link_space = uniform_dim_multi) + @test is_umlink isa IndsNetwork + @test all(map(x -> dim.(x) == uniform_dim_multi, edge_data(is_umlink))) + @test isempty(vertex_data(is_umlink)) + # initialize sites and links + is_usite_umlink = IndsNetwork(c; site_space = uniform_dim, link_space = uniform_dim_multi) + @test is_usite_umlink isa IndsNetwork + @test all(map(x -> dim.(x) == [uniform_dim], vertex_data(is_usite_umlink))) + @test all(map(x -> dim.(x) == uniform_dim_multi, edge_data(is_usite_umlink))) - # specify site spaces as Dictionary of dims or indices, and/or link spaces uniformly - site_dim_map = Dictionary(vertices(c), site_dims) - site_inds_map = Dictionary(vertices(c), site_inds) - site_dim_map_multi = Dictionary(vertices(c), site_dims_multi) - site_inds_map_multi = Dictionary(vertices(c), site_inds_multi) - # integer site dimensions, no links - is_site = IndsNetwork(c; site_space=site_dim_map) - @test is_site isa IndsNetwork - @test all(dim.(is_site[v]) == [site_dim_map[v]] for v in vertices(is_site)) - @test isempty(edge_data(is_site)) - is_msite = IndsNetwork(c; site_space=site_dim_map_multi) - @test is_msite isa IndsNetwork - @test all(dim.(is_msite[v]) == site_dim_map_multi[v] for v in vertices(is_msite)) - @test isempty(edge_data(is_msite)) - # integer site vector, uniform integer links - is_msite_ulink = IndsNetwork(c; site_space=site_dim_map_multi, link_space=uniform_dim) - @test is_msite_ulink isa IndsNetwork - @test all( - dim.(is_msite_ulink[v]) == site_dim_map_multi[v] for v in vertices(is_msite_ulink) - ) - @test all(map(x -> dim.(x) == [uniform_dim], edge_data(is_msite_ulink))) - # Index site map, no links - is_isite = IndsNetwork(c; site_space=site_inds_map) - @test is_isite isa IndsNetwork - @test all(is_isite[v] == [site_inds_map[v]] for v in vertices(is_isite)) - @test isempty(edge_data(is_isite)) - is_misite = IndsNetwork(c; site_space=site_inds_map_multi) - @test is_misite isa IndsNetwork - @test all(is_misite[v] == site_inds_map_multi[v] for v in vertices(is_misite)) - @test isempty(edge_data(is_misite)) - # Index site vector, uniform integer links - is_misite_ulink = IndsNetwork(c; site_space=site_inds_map_multi, link_space=uniform_dim) - @test is_misite_ulink isa IndsNetwork - @test all(is_misite_ulink[v] == site_inds_map_multi[v] for v in vertices(is_misite_ulink)) - @test all(map(x -> dim.(x) == [uniform_dim], edge_data(is_misite_ulink))) + # specify site spaces as Dictionary of dims or indices, and/or link spaces uniformly + site_dim_map = Dictionary(vertices(c), site_dims) + site_inds_map = Dictionary(vertices(c), site_inds) + site_dim_map_multi = Dictionary(vertices(c), site_dims_multi) + site_inds_map_multi = Dictionary(vertices(c), site_inds_multi) + # integer site dimensions, no links + is_site = IndsNetwork(c; site_space = site_dim_map) + @test is_site isa IndsNetwork + @test all(dim.(is_site[v]) == [site_dim_map[v]] for v in vertices(is_site)) + @test isempty(edge_data(is_site)) + is_msite = IndsNetwork(c; site_space = site_dim_map_multi) + @test is_msite isa IndsNetwork + @test all(dim.(is_msite[v]) == site_dim_map_multi[v] for v in vertices(is_msite)) + @test isempty(edge_data(is_msite)) + # integer site vector, uniform integer links + is_msite_ulink = IndsNetwork(c; site_space = site_dim_map_multi, link_space = uniform_dim) + @test is_msite_ulink isa IndsNetwork + @test all( + dim.(is_msite_ulink[v]) == site_dim_map_multi[v] for v in vertices(is_msite_ulink) + ) + @test all(map(x -> dim.(x) == [uniform_dim], edge_data(is_msite_ulink))) + # Index site map, no links + is_isite = IndsNetwork(c; site_space = site_inds_map) + @test is_isite isa IndsNetwork + @test all(is_isite[v] == [site_inds_map[v]] for v in vertices(is_isite)) + @test isempty(edge_data(is_isite)) + is_misite = IndsNetwork(c; site_space = site_inds_map_multi) + @test is_misite isa IndsNetwork + @test all(is_misite[v] == site_inds_map_multi[v] for v in vertices(is_misite)) + @test isempty(edge_data(is_misite)) + # Index site vector, uniform integer links + is_misite_ulink = IndsNetwork(c; site_space = site_inds_map_multi, link_space = uniform_dim) + @test is_misite_ulink isa IndsNetwork + @test all(is_misite_ulink[v] == site_inds_map_multi[v] for v in vertices(is_misite_ulink)) + @test all(map(x -> dim.(x) == [uniform_dim], edge_data(is_misite_ulink))) - # specify site spaces as Dictionary of dims or indices, and/or link spaces as Dictionary - # of indices - link_dim_map = Dictionary(edges(c), link_dims) - link_inds_map = Dictionary(edges(c), link_inds) - link_dim_map_multi = Dictionary(edges(c), link_dims_multi) - link_inds_map_multi = Dictionary(edges(c), link_inds_multi) - # index site dict, integer link dict - is_isite_link = IndsNetwork(c; site_space=site_inds_map, link_space=link_dim_map) - @test is_isite_link isa IndsNetwork - @test all(is_isite_link[v] == [site_inds_map[v]] for v in vertices(is_isite_link)) - @test all(dim.(is_isite_link[e]) == [link_dim_map[e]] for e in edges(is_isite_link)) - @test all(e -> dim.(is_isite_link[e]) == [link_dim_map[e]], keys(link_dim_map)) - is_isite_mlink = IndsNetwork(c; site_space=site_inds_map, link_space=link_dim_map_multi) - @test is_isite_mlink isa IndsNetwork - @test all(is_isite_mlink[v] == [site_inds_map[v]] for v in vertices(is_isite_mlink)) - @test all(dim.(is_isite_mlink[e]) == link_dim_map_multi[e] for e in edges(is_isite_mlink)) - @test all(e -> dim.(is_isite_mlink[e]) == link_dim_map_multi[e], keys(link_dim_map_multi)) - # index site dict, index link dict - is_misite_ilink = IndsNetwork(c; site_space=site_inds_map_multi, link_space=link_inds_map) - @test is_misite_ilink isa IndsNetwork - @test all(is_misite_ilink[v] == site_inds_map_multi[v] for v in vertices(is_misite_ilink)) - @test all(is_misite_ilink[e] == [link_inds_map[e]] for e in edges(is_misite_ilink)) - @test all(e -> is_misite_ilink[e] == [link_inds_map[e]], keys(link_inds_map)) - is_misite_milink = IndsNetwork( - c; site_space=site_inds_map_multi, link_space=link_inds_map_multi - ) - @test is_misite_ilink isa IndsNetwork - @test all( - is_misite_milink[v] == site_inds_map_multi[v] for v in vertices(is_misite_milink) - ) - @test all(is_misite_milink[e] == link_inds_map_multi[e] for e in edges(is_misite_milink)) - @test all(e -> is_misite_milink[e] == link_inds_map_multi[e], keys(link_inds_map_multi)) + # specify site spaces as Dictionary of dims or indices, and/or link spaces as Dictionary + # of indices + link_dim_map = Dictionary(edges(c), link_dims) + link_inds_map = Dictionary(edges(c), link_inds) + link_dim_map_multi = Dictionary(edges(c), link_dims_multi) + link_inds_map_multi = Dictionary(edges(c), link_inds_multi) + # index site dict, integer link dict + is_isite_link = IndsNetwork(c; site_space = site_inds_map, link_space = link_dim_map) + @test is_isite_link isa IndsNetwork + @test all(is_isite_link[v] == [site_inds_map[v]] for v in vertices(is_isite_link)) + @test all(dim.(is_isite_link[e]) == [link_dim_map[e]] for e in edges(is_isite_link)) + @test all(e -> dim.(is_isite_link[e]) == [link_dim_map[e]], keys(link_dim_map)) + is_isite_mlink = IndsNetwork(c; site_space = site_inds_map, link_space = link_dim_map_multi) + @test is_isite_mlink isa IndsNetwork + @test all(is_isite_mlink[v] == [site_inds_map[v]] for v in vertices(is_isite_mlink)) + @test all(dim.(is_isite_mlink[e]) == link_dim_map_multi[e] for e in edges(is_isite_mlink)) + @test all(e -> dim.(is_isite_mlink[e]) == link_dim_map_multi[e], keys(link_dim_map_multi)) + # index site dict, index link dict + is_misite_ilink = IndsNetwork(c; site_space = site_inds_map_multi, link_space = link_inds_map) + @test is_misite_ilink isa IndsNetwork + @test all(is_misite_ilink[v] == site_inds_map_multi[v] for v in vertices(is_misite_ilink)) + @test all(is_misite_ilink[e] == [link_inds_map[e]] for e in edges(is_misite_ilink)) + @test all(e -> is_misite_ilink[e] == [link_inds_map[e]], keys(link_inds_map)) + is_misite_milink = IndsNetwork( + c; site_space = site_inds_map_multi, link_space = link_inds_map_multi + ) + @test is_misite_ilink isa IndsNetwork + @test all( + is_misite_milink[v] == site_inds_map_multi[v] for v in vertices(is_misite_milink) + ) + @test all(is_misite_milink[e] == link_inds_map_multi[e] for e in edges(is_misite_milink)) + @test all(e -> is_misite_milink[e] == link_inds_map_multi[e], keys(link_inds_map_multi)) end @testset "IndsNetwork merging" begin - # test on comb tree - dims = (3, 2) - c = named_comb_tree(dims) - rng = StableRNG(1234) - site_dims1 = Dictionary( - vertices(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:nv(c)] - ) - site_dims2 = Dictionary( - vertices(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:nv(c)] - ) - link_dims1 = Dictionary( - edges(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:ne(c)] - ) - link_dims2 = Dictionary( - edges(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:ne(c)] - ) - is1_s = IndsNetwork(c; site_space=site_dims1) - is2_s = IndsNetwork(c; site_space=site_dims2) - is1_e = IndsNetwork(c; link_space=link_dims1) - is2_e = IndsNetwork(c; link_space=link_dims2) - is1 = IndsNetwork(c; site_space=site_dims1, link_space=link_dims1) - is2 = IndsNetwork(c; site_space=site_dims2, link_space=link_dims2) - # merge some networks - is1_m = union_all_inds(is1_s, is1_e) - @test dim.(vertex_data(is1_m)) == dim.(vertex_data(is1)) - is_ms = union_all_inds(is1_s, is2_s) - @test all(issetequal(is_ms[v], union(is1_s[v], is2_s[v])) for v in vertices(c)) - @test isempty(edge_data(is_ms)) - is_me = union_all_inds(is1_e, is2_e) - @test all(issetequal(is_me[e], union(is1_e[e], is2_e[e])) for e in edges(c)) - @test isempty(vertex_data(is_me)) - is_m = union_all_inds(is1, is2) - @test all(issetequal(is_m[v], union(is1[v], is2[v])) for v in vertices(c)) - @test all(issetequal(is_m[e], union(is1[e], is2[e])) for e in edges(c)) + # test on comb tree + dims = (3, 2) + c = named_comb_tree(dims) + rng = StableRNG(1234) + site_dims1 = Dictionary( + vertices(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:nv(c)] + ) + site_dims2 = Dictionary( + vertices(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:nv(c)] + ) + link_dims1 = Dictionary( + edges(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:ne(c)] + ) + link_dims2 = Dictionary( + edges(c), [[rand(rng, 2:6) for ni in 1:rand(rng, 1:3)] for _ in 1:ne(c)] + ) + is1_s = IndsNetwork(c; site_space = site_dims1) + is2_s = IndsNetwork(c; site_space = site_dims2) + is1_e = IndsNetwork(c; link_space = link_dims1) + is2_e = IndsNetwork(c; link_space = link_dims2) + is1 = IndsNetwork(c; site_space = site_dims1, link_space = link_dims1) + is2 = IndsNetwork(c; site_space = site_dims2, link_space = link_dims2) + # merge some networks + is1_m = union_all_inds(is1_s, is1_e) + @test dim.(vertex_data(is1_m)) == dim.(vertex_data(is1)) + is_ms = union_all_inds(is1_s, is2_s) + @test all(issetequal(is_ms[v], union(is1_s[v], is2_s[v])) for v in vertices(c)) + @test isempty(edge_data(is_ms)) + is_me = union_all_inds(is1_e, is2_e) + @test all(issetequal(is_me[e], union(is1_e[e], is2_e[e])) for e in edges(c)) + @test isempty(vertex_data(is_me)) + is_m = union_all_inds(is1, is2) + @test all(issetequal(is_m[v], union(is1[v], is2[v])) for v in vertices(c)) + @test all(issetequal(is_m[e], union(is1[e], is2[e])) for e in edges(c)) end end diff --git a/test/test_inner.jl b/test/test_inner.jl index 1b91753f..6e1d8066 100644 --- a/test/test_inner.jl +++ b/test/test_inner.jl @@ -1,15 +1,15 @@ @eval module $(gensym()) using ITensorNetworks: - ITensorNetwork, - inner, - inner_network, - loginner, - logscalar, - random_tensornetwork, - scalar, - siteinds, - ttn, - underlying_graph + ITensorNetwork, + inner, + inner_network, + loginner, + logscalar, + random_tensornetwork, + scalar, + siteinds, + ttn, + underlying_graph using ITensorNetworks.ModelHamiltonians: heisenberg using ITensors: dag using SplitApplyCombine: group @@ -19,41 +19,41 @@ using StableRNGs: StableRNG using TensorOperations: TensorOperations using Test: @test, @testset @testset "Inner products, BP vs exact comparison" begin - L = 4 - χ = 2 - g = NamedGraph(SimpleGraph(uniform_tree(L))) - s = siteinds("S=1/2", g) - rng = StableRNG(1234) - y = random_tensornetwork(rng, s; link_space=χ) - x = random_tensornetwork(rng, s; link_space=χ) + L = 4 + χ = 2 + g = NamedGraph(SimpleGraph(uniform_tree(L))) + s = siteinds("S=1/2", g) + rng = StableRNG(1234) + y = random_tensornetwork(rng, s; link_space = χ) + x = random_tensornetwork(rng, s; link_space = χ) - #First lets do it with the flattened version of the network - xy = inner_network(x, y) - xy_scalar = scalar(xy) - xy_scalar_bp = scalar(xy; alg="bp") - xy_scalar_logbp = exp(logscalar(xy; alg="bp")) + #First lets do it with the flattened version of the network + xy = inner_network(x, y) + xy_scalar = scalar(xy) + xy_scalar_bp = scalar(xy; alg = "bp") + xy_scalar_logbp = exp(logscalar(xy; alg = "bp")) - @test xy_scalar ≈ xy_scalar_bp - @test xy_scalar_bp ≈ xy_scalar_logbp - @test xy_scalar ≈ xy_scalar_logbp + @test xy_scalar ≈ xy_scalar_bp + @test xy_scalar_bp ≈ xy_scalar_logbp + @test xy_scalar ≈ xy_scalar_logbp - #Now lets do it via the inner function - xy_scalar = inner(x, y; alg="exact") - xy_scalar_bp = inner(x, y; alg="bp") - xy_scalar_logbp = exp(loginner(x, y; alg="bp")) + #Now lets do it via the inner function + xy_scalar = inner(x, y; alg = "exact") + xy_scalar_bp = inner(x, y; alg = "bp") + xy_scalar_logbp = exp(loginner(x, y; alg = "bp")) - @test xy_scalar ≈ xy_scalar_bp - @test xy_scalar_bp ≈ xy_scalar_logbp - @test xy_scalar ≈ xy_scalar_logbp + @test xy_scalar ≈ xy_scalar_bp + @test xy_scalar_bp ≈ xy_scalar_logbp + @test xy_scalar ≈ xy_scalar_logbp - #test contraction of three layers for expectation values - A = ITensorNetwork(ttn(heisenberg(g), s)) - xAy_scalar = inner(x, A, y; alg="exact") - xAy_scalar_bp = inner(x, A, y; alg="bp") - xAy_scalar_logbp = exp(loginner(x, A, y; alg="bp")) + #test contraction of three layers for expectation values + A = ITensorNetwork(ttn(heisenberg(g), s)) + xAy_scalar = inner(x, A, y; alg = "exact") + xAy_scalar_bp = inner(x, A, y; alg = "bp") + xAy_scalar_logbp = exp(loginner(x, A, y; alg = "bp")) - @test xAy_scalar ≈ xAy_scalar_bp - @test xAy_scalar_bp ≈ xAy_scalar_logbp - @test xAy_scalar ≈ xAy_scalar_logbp + @test xAy_scalar ≈ xAy_scalar_bp + @test xAy_scalar_bp ≈ xAy_scalar_logbp + @test xAy_scalar ≈ xAy_scalar_logbp end end diff --git a/test/test_itensornetwork.jl b/test/test_itensornetwork.jl index 47199f29..3441240d 100644 --- a/test/test_itensornetwork.jl +++ b/test/test_itensornetwork.jl @@ -2,58 +2,58 @@ using Dictionaries: Dictionary using Distributions: Uniform using Graphs: - degree, - dijkstra_shortest_paths, - edges, - grid, - has_vertex, - ne, - neighbors, - nv, - rem_vertex!, - vertices, - weights + degree, + dijkstra_shortest_paths, + edges, + grid, + has_vertex, + ne, + neighbors, + nv, + rem_vertex!, + vertices, + weights using GraphsFlows: GraphsFlows using ITensors: - ITensors, - Index, - ITensor, - Op, - commonind, - commoninds, - contract, - dag, - hascommoninds, - hasinds, - inds, - inner, - itensor, - onehot, - order, - prime, - random_itensor, - scalartype, - sim, - uniqueinds + ITensors, + Index, + ITensor, + Op, + commonind, + commoninds, + contract, + dag, + hascommoninds, + hasinds, + inds, + inner, + itensor, + onehot, + order, + prime, + random_itensor, + scalartype, + sim, + uniqueinds using ITensors.NDTensors: NDTensors, dim using ITensorNetworks: - ITensorNetworks, - ⊗, - IndsNetwork, - ITensorNetwork, - contraction_sequence, - flatten_linkinds, - flatten_siteinds, - inner_network, - linkinds, - neighbor_tensors, - norm_sqr, - norm_sqr_network, - orthogonalize, - random_tensornetwork, - siteinds, - tree_orthogonalize, - ttn + ITensorNetworks, + ⊗, + IndsNetwork, + ITensorNetwork, + contraction_sequence, + flatten_linkinds, + flatten_siteinds, + inner_network, + linkinds, + neighbor_tensors, + norm_sqr, + norm_sqr_network, + orthogonalize, + random_tensornetwork, + siteinds, + tree_orthogonalize, + ttn using LinearAlgebra: factorize using NamedGraphs: NamedEdge using NamedGraphs.GraphsExtensions: disjoint_union, incident_edges @@ -65,340 +65,340 @@ using TensorOperations: TensorOperations using Test: @test, @test_broken, @testset const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @testset "ITensorNetwork tests" begin - @testset "ITensorNetwork Basics" begin - g = named_grid((4,)) - s = siteinds("S=1/2", g) - @test s isa IndsNetwork - @test nv(s) == 4 - @test ne(s) == 3 - @test neighbors(s, (2,)) == [(1,), (3,)] - tn = ITensorNetwork(s; link_space=2) - @test nv(tn) == 4 - @test ne(tn) == 3 - @test tn isa ITensorNetwork - @test neighbors(tn, (2,)) == [(1,), (3,)] - - # TODO: How to support this syntax? - @test_broken tn[1] isa ITensor - @test tn[(1,)] isa ITensor - - # TODO: How to support this syntax? - @test_broken order(tn[1]) == 2 - @test order(tn[(1,)]) == 2 - - # TODO: How to support this syntax? - @test_broken tn[2] isa ITensor - @test tn[(2,)] isa ITensor - - # TODO: How to support this syntax? - @test_broken order(tn[2]) == 3 - @test order(tn[(2,)]) == 3 - - # XXX: Slicing syntax is no long supported, use `induced_subgraph`. - @test_broken tn[1:2] isa ITensorNetwork - # TODO: Support this syntax, maybe rename `subgraph`. - @test_broken induced_subgraph(tn, [(1,), (2,)]) isa ITensorNetwork - rng = StableRNG(1234) - for v in vertices(tn) - tn[v] = randn!(rng, tn[v]) - end - tn′ = sim(dag(tn); sites=[]) - - @test tn′ isa ITensorNetwork - inner_tn = tn ⊗ tn′ - @test inner_tn isa ITensorNetwork - sequence = contraction_sequence(inner_tn) - @test sequence isa Vector - inner_res = contract(inner_tn; sequence)[] - @test inner_res isa Float64 - - # test that by default vertices are linked by bond-dimension 1 index - tn = ITensorNetwork(s) - @test isone(ITensors.dim(commonind(tn[(1,)], tn[(2,)]))) - end - - @testset "Constructors from ITensors" begin - i, j, k, l = Index.(fill(2, 4)) - A = ITensor(i, j) - B = ITensor(j, k) - C = ITensor(k, l) - - tn = ITensorNetwork([A, B, C]) - @test issetequal(vertices(tn), [1, 2, 3]) - @test issetequal(edges(tn), NamedEdge.([1 => 2, 2 => 3])) - - tn = ITensorNetwork(["A", "B", "C"], [A, B, C]) - @test issetequal(vertices(tn), ["A", "B", "C"]) - @test issetequal(edges(tn), NamedEdge.(["A" => "B", "B" => "C"])) - - tn = ITensorNetwork(["A" => A, "B" => B, "C" => C]) - @test issetequal(vertices(tn), ["A", "B", "C"]) - @test issetequal(edges(tn), NamedEdge.(["A" => "B", "B" => "C"])) - end - - @testset "Contract edge (regression test for issue #5)" begin - dims = (2, 2) - g = named_grid(dims) - s = siteinds("S=1/2", g) - ψ = ITensorNetwork(v -> "↑", s) - tn = disjoint_union("bra" => ψ, "ket" => prime(dag(ψ); sites=[])) - tn_2 = contract(tn, ((1, 2), "ket") => ((1, 2), "bra")) - @test !has_vertex(tn_2, ((1, 2), "ket")) - @test tn_2[((1, 2), "bra")] ≈ tn[((1, 2), "ket")] * tn[((1, 2), "bra")] - end - - @testset "Remove edge (regression test for issue #5)" begin - dims = (2, 2) - g = named_grid(dims) - s = siteinds("S=1/2", g) - ψ = ITensorNetwork(v -> "↑", s) - rem_vertex!(ψ, (1, 2)) - tn = norm_sqr_network(ψ) - @test !has_vertex(tn, ((1, 2), "bra")) - @test !has_vertex(tn, ((1, 2), "ket")) - @test has_vertex(tn, ((1, 1), "bra")) - @test has_vertex(tn, ((1, 1), "ket")) - @test has_vertex(tn, ((2, 1), "bra")) - @test has_vertex(tn, ((2, 1), "ket")) - @test has_vertex(tn, ((2, 2), "bra")) - @test has_vertex(tn, ((2, 2), "ket")) - end - - @testset "Custom element type (eltype=$elt)" for elt in elts, - kwargs in ((;), (; link_space=3)), - g in ( - grid((4,)), - named_grid((3, 3)), - siteinds("S=1/2", grid((4,))), - siteinds("S=1/2", named_grid((3, 3))), - ) - - rng = StableRNG(1234) - ψ = ITensorNetwork(g; kwargs...) do v - return inds -> itensor(randn(rng, elt, dim.(inds)...), inds) + @testset "ITensorNetwork Basics" begin + g = named_grid((4,)) + s = siteinds("S=1/2", g) + @test s isa IndsNetwork + @test nv(s) == 4 + @test ne(s) == 3 + @test neighbors(s, (2,)) == [(1,), (3,)] + tn = ITensorNetwork(s; link_space = 2) + @test nv(tn) == 4 + @test ne(tn) == 3 + @test tn isa ITensorNetwork + @test neighbors(tn, (2,)) == [(1,), (3,)] + + # TODO: How to support this syntax? + @test_broken tn[1] isa ITensor + @test tn[(1,)] isa ITensor + + # TODO: How to support this syntax? + @test_broken order(tn[1]) == 2 + @test order(tn[(1,)]) == 2 + + # TODO: How to support this syntax? + @test_broken tn[2] isa ITensor + @test tn[(2,)] isa ITensor + + # TODO: How to support this syntax? + @test_broken order(tn[2]) == 3 + @test order(tn[(2,)]) == 3 + + # XXX: Slicing syntax is no long supported, use `induced_subgraph`. + @test_broken tn[1:2] isa ITensorNetwork + # TODO: Support this syntax, maybe rename `subgraph`. + @test_broken induced_subgraph(tn, [(1,), (2,)]) isa ITensorNetwork + rng = StableRNG(1234) + for v in vertices(tn) + tn[v] = randn!(rng, tn[v]) + end + tn′ = sim(dag(tn); sites = []) + + @test tn′ isa ITensorNetwork + inner_tn = tn ⊗ tn′ + @test inner_tn isa ITensorNetwork + sequence = contraction_sequence(inner_tn) + @test sequence isa Vector + inner_res = contract(inner_tn; sequence)[] + @test inner_res isa Float64 + + # test that by default vertices are linked by bond-dimension 1 index + tn = ITensorNetwork(s) + @test isone(ITensors.dim(commonind(tn[(1,)], tn[(2,)]))) end - @test eltype(ψ[first(vertices(ψ))]) == elt - ψc = conj(ψ) - for v in vertices(ψ) - @test ψc[v] == conj(ψ[v]) + @testset "Constructors from ITensors" begin + i, j, k, l = Index.(fill(2, 4)) + A = ITensor(i, j) + B = ITensor(j, k) + C = ITensor(k, l) + + tn = ITensorNetwork([A, B, C]) + @test issetequal(vertices(tn), [1, 2, 3]) + @test issetequal(edges(tn), NamedEdge.([1 => 2, 2 => 3])) + + tn = ITensorNetwork(["A", "B", "C"], [A, B, C]) + @test issetequal(vertices(tn), ["A", "B", "C"]) + @test issetequal(edges(tn), NamedEdge.(["A" => "B", "B" => "C"])) + + tn = ITensorNetwork(["A" => A, "B" => B, "C" => C]) + @test issetequal(vertices(tn), ["A", "B", "C"]) + @test issetequal(edges(tn), NamedEdge.(["A" => "B", "B" => "C"])) end - ψd = dag(ψ) - for v in vertices(ψ) - @test ψd[v] == dag(ψ[v]) + @testset "Contract edge (regression test for issue #5)" begin + dims = (2, 2) + g = named_grid(dims) + s = siteinds("S=1/2", g) + ψ = ITensorNetwork(v -> "↑", s) + tn = disjoint_union("bra" => ψ, "ket" => prime(dag(ψ); sites = [])) + tn_2 = contract(tn, ((1, 2), "ket") => ((1, 2), "bra")) + @test !has_vertex(tn_2, ((1, 2), "ket")) + @test tn_2[((1, 2), "bra")] ≈ tn[((1, 2), "ket")] * tn[((1, 2), "bra")] end - rng = StableRNG(1234) - ψ = ITensorNetwork(g; kwargs...) do v - return inds -> itensor(randn(rng, dim.(inds)...), inds) + @testset "Remove edge (regression test for issue #5)" begin + dims = (2, 2) + g = named_grid(dims) + s = siteinds("S=1/2", g) + ψ = ITensorNetwork(v -> "↑", s) + rem_vertex!(ψ, (1, 2)) + tn = norm_sqr_network(ψ) + @test !has_vertex(tn, ((1, 2), "bra")) + @test !has_vertex(tn, ((1, 2), "ket")) + @test has_vertex(tn, ((1, 1), "bra")) + @test has_vertex(tn, ((1, 1), "ket")) + @test has_vertex(tn, ((2, 1), "bra")) + @test has_vertex(tn, ((2, 1), "ket")) + @test has_vertex(tn, ((2, 2), "bra")) + @test has_vertex(tn, ((2, 2), "ket")) end - @test eltype(ψ[first(vertices(ψ))]) == Float64 - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, elt, g; kwargs...) - @test eltype(ψ[first(vertices(ψ))]) == elt - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, g; kwargs...) - @test eltype(ψ[first(vertices(ψ))]) == Float64 - ψ = ITensorNetwork(elt, undef, g; kwargs...) - @test eltype(ψ[first(vertices(ψ))]) == elt - ψ = ITensorNetwork(undef, g) - @test eltype(ψ[first(vertices(ψ))]) == Float64 - end - - @testset "Product state constructors" for elt in elts - dims = (2, 2) - g = named_comb_tree(dims) - s = siteinds("S=1/2", g) - state1 = ["↑" "↓"; "↓" "↑"] - state2 = reshape([[1, 0], [0, 1], [0, 1], [1, 0]], 2, 2) - each_args = (; - ferro=( - ("↑",), - (elt, "↑"), - (Returns(i -> ITensor([1, 0], i)),), - (elt, Returns(i -> ITensor([1, 0], i))), - (Returns([1, 0]),), - (elt, Returns([1, 0])), - ), - antiferro=( - (state1,), - (elt, state1), - (Dict(CartesianIndices(dims) .=> state1),), - (elt, Dict(CartesianIndices(dims) .=> state1)), - (Dict(Tuple.(CartesianIndices(dims)) .=> state1),), - (elt, Dict(Tuple.(CartesianIndices(dims)) .=> state1)), - (Dictionary(CartesianIndices(dims), state1),), - (elt, Dictionary(CartesianIndices(dims), state1)), - (Dictionary(Tuple.(CartesianIndices(dims)), state1),), - (elt, Dictionary(Tuple.(CartesianIndices(dims)), state1)), - (state2,), - (elt, state2), - (Dict(CartesianIndices(dims) .=> state2),), - (elt, Dict(CartesianIndices(dims) .=> state2)), - (Dict(Tuple.(CartesianIndices(dims)) .=> state2),), - (elt, Dict(Tuple.(CartesianIndices(dims)) .=> state2)), - (Dictionary(CartesianIndices(dims), state2),), - (elt, Dictionary(CartesianIndices(dims), state2)), - (Dictionary(Tuple.(CartesianIndices(dims)), state2),), - (elt, Dictionary(Tuple.(CartesianIndices(dims)), state2)), - ), - ) - for pattern in keys(each_args) - for args in each_args[pattern] - x = ITensorNetwork(args..., s) - if first(args) === elt - @test scalartype(x) === elt - else - @test scalartype(x) === Float64 + + @testset "Custom element type (eltype=$elt)" for elt in elts, + kwargs in ((;), (; link_space = 3)), + g in ( + grid((4,)), + named_grid((3, 3)), + siteinds("S=1/2", grid((4,))), + siteinds("S=1/2", named_grid((3, 3))), + ) + + rng = StableRNG(1234) + ψ = ITensorNetwork(g; kwargs...) do v + return inds -> itensor(randn(rng, elt, dim.(inds)...), inds) + end + @test eltype(ψ[first(vertices(ψ))]) == elt + + ψc = conj(ψ) + for v in vertices(ψ) + @test ψc[v] == conj(ψ[v]) + end + + ψd = dag(ψ) + for v in vertices(ψ) + @test ψd[v] == dag(ψ[v]) + end + + rng = StableRNG(1234) + ψ = ITensorNetwork(g; kwargs...) do v + return inds -> itensor(randn(rng, dim.(inds)...), inds) end - for v in vertices(x) - xᵛ = x[v] - @test degree(x, v) + 1 == ndims(xᵛ) - sᵛ = only(siteinds(x, v)) - for w in neighbors(x, v) - lʷ = only(linkinds(x, v => w)) - @test dim(lʷ) == 1 - xᵛ *= onehot(lʷ => 1) - end - @test ndims(xᵛ) == 1 - a = if pattern == :ferro - [1, 0] - elseif pattern == :antiferro - iseven(sum(v)) ? [1, 0] : [0, 1] - end - @test xᵛ == ITensor(a, sᵛ) + @test eltype(ψ[first(vertices(ψ))]) == Float64 + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, elt, g; kwargs...) + @test eltype(ψ[first(vertices(ψ))]) == elt + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, g; kwargs...) + @test eltype(ψ[first(vertices(ψ))]) == Float64 + ψ = ITensorNetwork(elt, undef, g; kwargs...) + @test eltype(ψ[first(vertices(ψ))]) == elt + ψ = ITensorNetwork(undef, g) + @test eltype(ψ[first(vertices(ψ))]) == Float64 + end + + @testset "Product state constructors" for elt in elts + dims = (2, 2) + g = named_comb_tree(dims) + s = siteinds("S=1/2", g) + state1 = ["↑" "↓"; "↓" "↑"] + state2 = reshape([[1, 0], [0, 1], [0, 1], [1, 0]], 2, 2) + each_args = (; + ferro = ( + ("↑",), + (elt, "↑"), + (Returns(i -> ITensor([1, 0], i)),), + (elt, Returns(i -> ITensor([1, 0], i))), + (Returns([1, 0]),), + (elt, Returns([1, 0])), + ), + antiferro = ( + (state1,), + (elt, state1), + (Dict(CartesianIndices(dims) .=> state1),), + (elt, Dict(CartesianIndices(dims) .=> state1)), + (Dict(Tuple.(CartesianIndices(dims)) .=> state1),), + (elt, Dict(Tuple.(CartesianIndices(dims)) .=> state1)), + (Dictionary(CartesianIndices(dims), state1),), + (elt, Dictionary(CartesianIndices(dims), state1)), + (Dictionary(Tuple.(CartesianIndices(dims)), state1),), + (elt, Dictionary(Tuple.(CartesianIndices(dims)), state1)), + (state2,), + (elt, state2), + (Dict(CartesianIndices(dims) .=> state2),), + (elt, Dict(CartesianIndices(dims) .=> state2)), + (Dict(Tuple.(CartesianIndices(dims)) .=> state2),), + (elt, Dict(Tuple.(CartesianIndices(dims)) .=> state2)), + (Dictionary(CartesianIndices(dims), state2),), + (elt, Dictionary(CartesianIndices(dims), state2)), + (Dictionary(Tuple.(CartesianIndices(dims)), state2),), + (elt, Dictionary(Tuple.(CartesianIndices(dims)), state2)), + ), + ) + for pattern in keys(each_args) + for args in each_args[pattern] + x = ITensorNetwork(args..., s) + if first(args) === elt + @test scalartype(x) === elt + else + @test scalartype(x) === Float64 + end + for v in vertices(x) + xᵛ = x[v] + @test degree(x, v) + 1 == ndims(xᵛ) + sᵛ = only(siteinds(x, v)) + for w in neighbors(x, v) + lʷ = only(linkinds(x, v => w)) + @test dim(lʷ) == 1 + xᵛ *= onehot(lʷ => 1) + end + @test ndims(xᵛ) == 1 + a = if pattern == :ferro + [1, 0] + elseif pattern == :antiferro + iseven(sum(v)) ? [1, 0] : [0, 1] + end + @test xᵛ == ITensor(a, sᵛ) + end + end end - end end - end - @testset "random_tensornetwork with custom distributions" begin - distribution = Uniform(-1.0, 1.0) - rng = StableRNG(1234) - tn = random_tensornetwork(rng, distribution, named_grid(4); link_space=2) - # Note: distributions in package `Distributions` currently doesn't support customized - # eltype, and all elements have type `Float64` - @test eltype(tn[first(vertices(tn))]) == Float64 - end - @testset "orthogonalize" begin - rng = StableRNG(1234) - tn = random_tensornetwork(rng, named_grid(4); link_space=2) - Z = norm_sqr(tn) - tn_ortho = factorize(tn, 4 => 3) - # TODO: Error here in arranging the edges. Arrange by hash? - Z̃ = norm_sqr(tn_ortho) - @test nv(tn_ortho) == 5 - @test nv(tn) == 4 - @test Z ≈ Z̃ - tn_ortho = tree_orthogonalize(tn, [3, 4]) - Z̃ = norm_sqr(tn_ortho) - @test nv(tn_ortho) == 4 - @test nv(tn) == 4 - @test Z ≈ Z̃ - - tn_ortho = tree_orthogonalize(tn, 1) - Z̃ = norm_sqr(tn_ortho) - @test Z ≈ Z̃ - Z̃ = inner(tn_ortho, tn) - @test Z ≈ Z̃ - end - - @testset "dijkstra_shortest_paths" begin - tn = ITensorNetwork(named_grid(4); link_space=2) - paths = dijkstra_shortest_paths(tn, [1]) - @test paths.dists == Dictionary([0, 1, 2, 3]) - @test paths.parents == Dictionary([1, 1, 2, 3]) - @test paths.pathcounts == Dictionary([1.0, 1.0, 1.0, 1.0]) - end - - @testset "mincut" begin - tn = ITensorNetwork(named_grid(4); link_space=3) - w = weights(tn) - @test w isa Dictionary{Tuple{Int,Int},Float64} - @test length(w) ≈ ne(tn) - @test w[(1, 2)] ≈ log2(3) - @test w[(2, 3)] ≈ log2(3) - @test w[(3, 4)] ≈ log2(3) - p1, p2, wc = GraphsFlows.mincut(tn, 2, 3) - @test issetequal(p1, [1, 2]) - @test issetequal(p2, [3, 4]) - @test isone(wc) - - p1, p2, wc = GraphsFlows.mincut(tn, 2, 3, w) - @test issetequal(p1, [1, 2]) - @test issetequal(p2, [3, 4]) - @test wc ≈ log2(3) - end - - @testset "Index access" begin - dims = (2, 2) - g = named_grid(dims) - s = siteinds("S=1/2", g) - ψ = ITensorNetwork(s; link_space=2) - - nt = neighbor_tensors(ψ, (1, 1)) - @test length(nt) == 2 - @test all(map(hascommoninds(ψ[1, 1]), nt)) - - @test all(map(t -> isempty(commoninds(inds(t), uniqueinds(ψ, (1, 1)))), nt)) - - e = (1, 1) => (2, 1) - uie = uniqueinds(ψ, e) - @test isempty(commoninds(uie, inds(ψ[2, 1]))) - @test issetequal(uie, union(commoninds(ψ[1, 1], ψ[1, 2]), uniqueinds(ψ, (1, 1)))) - - @test siteinds(ψ, (1, 1)) == s[1, 1] - - cie = commoninds(ψ, e) - @test hasinds(ψ[1, 1], cie) && hasinds(ψ[2, 1], cie) - @test isempty(commoninds(uie, cie)) - - @test linkinds(ψ, e) == commoninds(ψ[1, 1], ψ[2, 1]) - - @test length(flatten_siteinds(ψ)) == length(vertices(g)) - @test length(flatten_linkinds(ψ)) == length(edges(g)) - end - - @testset "eltype conversion, $new_eltype" for new_eltype in (Float32, ComplexF64) - dims = (2, 2) - g = named_grid(dims) - s = siteinds("S=1/2", g) - rng = StableRNG(1234) - ψ = random_tensornetwork(rng, s; link_space=2) - @test scalartype(ψ) == Float64 - ϕ = NDTensors.convert_scalartype(new_eltype, ψ) - @test scalartype(ϕ) == new_eltype - end - - @testset "Construction from state map" for elt in (Float32, ComplexF64) - dims = (2, 2) - g = named_grid(dims) - s = siteinds("S=1/2", g) - state_map(v::Tuple) = iseven(sum(isodd.(v))) ? "↑" : "↓" - ψ = ITensorNetwork(state_map, s) - t = ψ[2, 2] - si = only(siteinds(ψ, (2, 2))) - bi = map(e -> only(linkinds(ψ, e)), incident_edges(ψ, (2, 2))) - @test eltype(t) == Float64 - @test abs(t[si => "↑", [b => end for b in bi]...]) == 1.0 # insert_links introduces extra signs through factorization... - @test t[si => "↓", [b => end for b in bi]...] == 0.0 - ϕ = ITensorNetwork(elt, state_map, s) - t = ϕ[2, 2] - si = only(siteinds(ϕ, (2, 2))) - bi = map(e -> only(linkinds(ϕ, e)), incident_edges(ϕ, (2, 2))) - @test eltype(t) == elt - @test abs(t[si => "↑", [b => end for b in bi]...]) == convert(elt, 1.0) # insert_links introduces extra signs through factorization... - @test t[si => "↓", [b => end for b in bi]...] == convert(elt, 0.0) - end - - @testset "Priming and tagging" begin - # TODO: add actual tests - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - is = siteinds("S=1/2", c) - rng = StableRNG(1234) - tn = random_tensornetwork(rng, is; link_space=3) - @test_broken swapprime(tn, 0, 2) - end + @testset "random_tensornetwork with custom distributions" begin + distribution = Uniform(-1.0, 1.0) + rng = StableRNG(1234) + tn = random_tensornetwork(rng, distribution, named_grid(4); link_space = 2) + # Note: distributions in package `Distributions` currently doesn't support customized + # eltype, and all elements have type `Float64` + @test eltype(tn[first(vertices(tn))]) == Float64 + end + @testset "orthogonalize" begin + rng = StableRNG(1234) + tn = random_tensornetwork(rng, named_grid(4); link_space = 2) + Z = norm_sqr(tn) + tn_ortho = factorize(tn, 4 => 3) + # TODO: Error here in arranging the edges. Arrange by hash? + Z̃ = norm_sqr(tn_ortho) + @test nv(tn_ortho) == 5 + @test nv(tn) == 4 + @test Z ≈ Z̃ + tn_ortho = tree_orthogonalize(tn, [3, 4]) + Z̃ = norm_sqr(tn_ortho) + @test nv(tn_ortho) == 4 + @test nv(tn) == 4 + @test Z ≈ Z̃ + + tn_ortho = tree_orthogonalize(tn, 1) + Z̃ = norm_sqr(tn_ortho) + @test Z ≈ Z̃ + Z̃ = inner(tn_ortho, tn) + @test Z ≈ Z̃ + end + + @testset "dijkstra_shortest_paths" begin + tn = ITensorNetwork(named_grid(4); link_space = 2) + paths = dijkstra_shortest_paths(tn, [1]) + @test paths.dists == Dictionary([0, 1, 2, 3]) + @test paths.parents == Dictionary([1, 1, 2, 3]) + @test paths.pathcounts == Dictionary([1.0, 1.0, 1.0, 1.0]) + end + + @testset "mincut" begin + tn = ITensorNetwork(named_grid(4); link_space = 3) + w = weights(tn) + @test w isa Dictionary{Tuple{Int, Int}, Float64} + @test length(w) ≈ ne(tn) + @test w[(1, 2)] ≈ log2(3) + @test w[(2, 3)] ≈ log2(3) + @test w[(3, 4)] ≈ log2(3) + p1, p2, wc = GraphsFlows.mincut(tn, 2, 3) + @test issetequal(p1, [1, 2]) + @test issetequal(p2, [3, 4]) + @test isone(wc) + + p1, p2, wc = GraphsFlows.mincut(tn, 2, 3, w) + @test issetequal(p1, [1, 2]) + @test issetequal(p2, [3, 4]) + @test wc ≈ log2(3) + end + + @testset "Index access" begin + dims = (2, 2) + g = named_grid(dims) + s = siteinds("S=1/2", g) + ψ = ITensorNetwork(s; link_space = 2) + + nt = neighbor_tensors(ψ, (1, 1)) + @test length(nt) == 2 + @test all(map(hascommoninds(ψ[1, 1]), nt)) + + @test all(map(t -> isempty(commoninds(inds(t), uniqueinds(ψ, (1, 1)))), nt)) + + e = (1, 1) => (2, 1) + uie = uniqueinds(ψ, e) + @test isempty(commoninds(uie, inds(ψ[2, 1]))) + @test issetequal(uie, union(commoninds(ψ[1, 1], ψ[1, 2]), uniqueinds(ψ, (1, 1)))) + + @test siteinds(ψ, (1, 1)) == s[1, 1] + + cie = commoninds(ψ, e) + @test hasinds(ψ[1, 1], cie) && hasinds(ψ[2, 1], cie) + @test isempty(commoninds(uie, cie)) + + @test linkinds(ψ, e) == commoninds(ψ[1, 1], ψ[2, 1]) + + @test length(flatten_siteinds(ψ)) == length(vertices(g)) + @test length(flatten_linkinds(ψ)) == length(edges(g)) + end + + @testset "eltype conversion, $new_eltype" for new_eltype in (Float32, ComplexF64) + dims = (2, 2) + g = named_grid(dims) + s = siteinds("S=1/2", g) + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = 2) + @test scalartype(ψ) == Float64 + ϕ = NDTensors.convert_scalartype(new_eltype, ψ) + @test scalartype(ϕ) == new_eltype + end + + @testset "Construction from state map" for elt in (Float32, ComplexF64) + dims = (2, 2) + g = named_grid(dims) + s = siteinds("S=1/2", g) + state_map(v::Tuple) = iseven(sum(isodd.(v))) ? "↑" : "↓" + ψ = ITensorNetwork(state_map, s) + t = ψ[2, 2] + si = only(siteinds(ψ, (2, 2))) + bi = map(e -> only(linkinds(ψ, e)), incident_edges(ψ, (2, 2))) + @test eltype(t) == Float64 + @test abs(t[si => "↑", [b => end for b in bi]...]) == 1.0 # insert_links introduces extra signs through factorization... + @test t[si => "↓", [b => end for b in bi]...] == 0.0 + ϕ = ITensorNetwork(elt, state_map, s) + t = ϕ[2, 2] + si = only(siteinds(ϕ, (2, 2))) + bi = map(e -> only(linkinds(ϕ, e)), incident_edges(ϕ, (2, 2))) + @test eltype(t) == elt + @test abs(t[si => "↑", [b => end for b in bi]...]) == convert(elt, 1.0) # insert_links introduces extra signs through factorization... + @test t[si => "↓", [b => end for b in bi]...] == convert(elt, 0.0) + end + + @testset "Priming and tagging" begin + # TODO: add actual tests + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + is = siteinds("S=1/2", c) + rng = StableRNG(1234) + tn = random_tensornetwork(rng, is; link_space = 3) + @test_broken swapprime(tn, 0, 2) + end end end diff --git a/test/test_itensornetworksadaptext.jl b/test/test_itensornetworksadaptext.jl index 0578511a..57ad284c 100644 --- a/test/test_itensornetworksadaptext.jl +++ b/test/test_itensornetworksadaptext.jl @@ -11,13 +11,13 @@ single_precision(type::Type{<:Complex}) = complex(single_precision(real(type))) Adapt.adapt_storage(::SinglePrecisionAdaptor, x) = single_precision(eltype(x)).(x) @testset "Test ITensorNetworksAdaptExt (eltype=$elt)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} -) - g = named_grid((2, 2)) - s = siteinds("S=1/2", g) - tn = random_tensornetwork(elt, s) - @test ITensors.scalartype(tn) === elt - tn′ = adapt(SinglePrecisionAdaptor(), tn) - @test ITensors.scalartype(tn′) === single_precision(elt) + Float32, Float64, Complex{Float32}, Complex{Float64}, + ) + g = named_grid((2, 2)) + s = siteinds("S=1/2", g) + tn = random_tensornetwork(elt, s) + @test ITensors.scalartype(tn) === elt + tn′ = adapt(SinglePrecisionAdaptor(), tn) + @test ITensors.scalartype(tn′) === single_precision(elt) end end diff --git a/test/test_itensorsextensions.jl b/test/test_itensorsextensions.jl index 39ab8005..eaca140a 100644 --- a/test/test_itensorsextensions.jl +++ b/test/test_itensorsextensions.jl @@ -1,142 +1,142 @@ @eval module $(gensym()) using ITensors: - ITensors, - ITensor, - Index, - QN, - apply, - dag, - delta, - inds, - mapprime, - noprime, - norm, - op, - permute, - prime, - random_itensor, - replaceind, - replaceinds, - sim, - swapprime + ITensors, + ITensor, + Index, + QN, + apply, + dag, + delta, + inds, + mapprime, + noprime, + norm, + op, + permute, + prime, + random_itensor, + replaceind, + replaceinds, + sim, + swapprime using ITensorNetworks.ITensorsExtensions: eigendecomp, map_eigvals using StableRNGs: StableRNG using Test: @test, @testset @testset "ITensorsExtensions" begin - @testset "Test map eigvals without QNS (eltype=$elt, dim=$n)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - n in (2, 3, 5, 10) - - i, j = Index(n, "i"), Index(n, "j") - linds, rinds = Index[i], Index[j] - rng = StableRNG(1234) - A = randn(rng, elt, (n, n)) - A = A * A' - P = ITensor(A, i, j) - sqrtP = map_eigvals(sqrt, P, linds, rinds; ishermitian=true) - inv_P = dag(map_eigvals(inv, P, linds, rinds; ishermitian=true)) - inv_sqrtP = dag(map_eigvals(inv ∘ sqrt, P, linds, rinds; ishermitian=true)) - - sqrtPdag = replaceind(dag(sqrtP), i, i') - P2 = replaceind(sqrtP * sqrtPdag, i', j) - @test P2 ≈ P - - invP = replaceind(inv_P, i, i') - I = invP * P - @test I ≈ delta(elt, inds(I)) - - inv_sqrtP = replaceind(inv_sqrtP, i, i') - I = inv_sqrtP * sqrtP - @test I ≈ delta(elt, inds(I)) - end - - @testset "Test map eigvals with QNS (eltype=$elt, dim=$n)" for elt in ( - Float32, Float64, Complex{Float32}, Complex{Float64} - ), - n in (2, 3, 5, 10) - - i, j = Index.(([QN() => n], [QN() => n])) - rng = StableRNG(1234) - A = random_itensor(rng, elt, i, j) - P = A * prime(dag(A), i) - sqrtP = map_eigvals(sqrt, P, [i], [i']; ishermitian=true) - inv_P = dag(map_eigvals(inv, P, [i], [i']; ishermitian=true)) - inv_sqrtP = dag(map_eigvals(inv ∘ sqrt, P, [i], [i']; ishermitian=true)) - - new_ind = noprime(sim(i')) - sqrtPdag = replaceind(dag(sqrtP), i', new_ind) - P2 = replaceind(sqrtP * sqrtPdag, new_ind, i) - @test P2 ≈ P - - inv_P = replaceind(inv_P, i', new_ind) - I = replaceind(inv_P * P, new_ind, i) - @test I ≈ op("I", i) - - inv_sqrtP = replaceind(inv_sqrtP, i', new_ind) - I = replaceind(inv_sqrtP * sqrtP, new_ind, i) - @test I ≈ op("I", i) - end - - @testset "Fermionic eigendecomp" begin - ITensors.enable_auto_fermion() - s1 = Index([QN("Nf", 0, -1)=>2, QN("Nf", 1, -1)=>2], "Site,Fermion,n=1") - s2 = Index([QN("Nf", 0, -1)=>2, QN("Nf", 1, -1)=>2], "Site,Fermion,n=2") - - # Make a random Hermitian matrix-like 4th order ITensor - T = random_itensor(s1', s2', dag(s2), dag(s1)) - T = apply(T, swapprime(dag(T), 0=>1)) - @test T ≈ swapprime(dag(T), 0=>1) # check Hermitian - - Ul, D, Ur = eigendecomp(T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian=true) - - @test Ul*D*Ur ≈ T - ITensors.disable_auto_fermion() - end - - @testset "Fermionic map eigvals tests" begin - ITensors.enable_auto_fermion() - s1 = Index([QN("Nf", 0, -1)=>2, QN("Nf", 1, -1)=>2], "Site,Fermion,n=1") - s2 = Index([QN("Nf", 0, -1)=>2, QN("Nf", 1, -1)=>2], "Site,Fermion,n=2") - - # Make a random Hermitian matrix ITensor - M = random_itensor(s1', dag(s1)) - #M = mapprime(prime(M)*swapprime(dag(M),0=>1),2=>1) - M = apply(M, swapprime(dag(M), 0=>1)) - - # Make a random Hermitian matrix-like 4th order ITensor - T = random_itensor(s1', s2', dag(s2), dag(s1)) - T = apply(T, swapprime(dag(T), 0=>1)) - - # Matrix test - sqrtM = map_eigvals(sqrt, M, [s1'], [dag(s1)]; ishermitian=true) - @test M ≈ apply(sqrtM, sqrtM) - - ## Tensor test - sqrtT = map_eigvals(sqrt, T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian=true) - @test T ≈ apply(sqrtT, sqrtT) - - # Permute and test again - T = permute(T, dag(s2), s2', dag(s1), s1') - sqrtT = map_eigvals(sqrt, T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian=true) - @test T ≈ apply(sqrtT, sqrtT) - - ## Explicitly passing indices in different, valid orders - sqrtT = map_eigvals(sqrt, T, [s2', s1'], [dag(s2), dag(s1)]; ishermitian=true) - @test T ≈ apply(sqrtT, sqrtT) - sqrtT = map_eigvals(sqrt, T, [dag(s2), dag(s1)], [s2', s1'], ; ishermitian=true) - @test T ≈ apply(sqrtT, sqrtT) - sqrtT = map_eigvals(sqrt, T, [dag(s1), dag(s2)], [s1', s2'], ; ishermitian=true) - @test T ≈ apply(sqrtT, sqrtT) - - # Test bosonic index case while fermion system is enabled - b = Index([QN("Nb", 0)=>2, QN("Nb", 1)=>2]) - T = random_itensor(b', dag(b)) - T = apply(T, swapprime(dag(T), 0=>1)) - sqrtT = map_eigvals(sqrt, T, [b'], [dag(b)]; ishermitian=true) - @test T ≈ apply(sqrtT, sqrtT) - - ITensors.disable_auto_fermion() - end + @testset "Test map eigvals without QNS (eltype=$elt, dim=$n)" for elt in ( + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + n in (2, 3, 5, 10) + + i, j = Index(n, "i"), Index(n, "j") + linds, rinds = Index[i], Index[j] + rng = StableRNG(1234) + A = randn(rng, elt, (n, n)) + A = A * A' + P = ITensor(A, i, j) + sqrtP = map_eigvals(sqrt, P, linds, rinds; ishermitian = true) + inv_P = dag(map_eigvals(inv, P, linds, rinds; ishermitian = true)) + inv_sqrtP = dag(map_eigvals(inv ∘ sqrt, P, linds, rinds; ishermitian = true)) + + sqrtPdag = replaceind(dag(sqrtP), i, i') + P2 = replaceind(sqrtP * sqrtPdag, i', j) + @test P2 ≈ P + + invP = replaceind(inv_P, i, i') + I = invP * P + @test I ≈ delta(elt, inds(I)) + + inv_sqrtP = replaceind(inv_sqrtP, i, i') + I = inv_sqrtP * sqrtP + @test I ≈ delta(elt, inds(I)) + end + + @testset "Test map eigvals with QNS (eltype=$elt, dim=$n)" for elt in ( + Float32, Float64, Complex{Float32}, Complex{Float64}, + ), + n in (2, 3, 5, 10) + + i, j = Index.(([QN() => n], [QN() => n])) + rng = StableRNG(1234) + A = random_itensor(rng, elt, i, j) + P = A * prime(dag(A), i) + sqrtP = map_eigvals(sqrt, P, [i], [i']; ishermitian = true) + inv_P = dag(map_eigvals(inv, P, [i], [i']; ishermitian = true)) + inv_sqrtP = dag(map_eigvals(inv ∘ sqrt, P, [i], [i']; ishermitian = true)) + + new_ind = noprime(sim(i')) + sqrtPdag = replaceind(dag(sqrtP), i', new_ind) + P2 = replaceind(sqrtP * sqrtPdag, new_ind, i) + @test P2 ≈ P + + inv_P = replaceind(inv_P, i', new_ind) + I = replaceind(inv_P * P, new_ind, i) + @test I ≈ op("I", i) + + inv_sqrtP = replaceind(inv_sqrtP, i', new_ind) + I = replaceind(inv_sqrtP * sqrtP, new_ind, i) + @test I ≈ op("I", i) + end + + @testset "Fermionic eigendecomp" begin + ITensors.enable_auto_fermion() + s1 = Index([QN("Nf", 0, -1) => 2, QN("Nf", 1, -1) => 2], "Site,Fermion,n=1") + s2 = Index([QN("Nf", 0, -1) => 2, QN("Nf", 1, -1) => 2], "Site,Fermion,n=2") + + # Make a random Hermitian matrix-like 4th order ITensor + T = random_itensor(s1', s2', dag(s2), dag(s1)) + T = apply(T, swapprime(dag(T), 0 => 1)) + @test T ≈ swapprime(dag(T), 0 => 1) # check Hermitian + + Ul, D, Ur = eigendecomp(T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian = true) + + @test Ul * D * Ur ≈ T + ITensors.disable_auto_fermion() + end + + @testset "Fermionic map eigvals tests" begin + ITensors.enable_auto_fermion() + s1 = Index([QN("Nf", 0, -1) => 2, QN("Nf", 1, -1) => 2], "Site,Fermion,n=1") + s2 = Index([QN("Nf", 0, -1) => 2, QN("Nf", 1, -1) => 2], "Site,Fermion,n=2") + + # Make a random Hermitian matrix ITensor + M = random_itensor(s1', dag(s1)) + #M = mapprime(prime(M)*swapprime(dag(M),0=>1),2=>1) + M = apply(M, swapprime(dag(M), 0 => 1)) + + # Make a random Hermitian matrix-like 4th order ITensor + T = random_itensor(s1', s2', dag(s2), dag(s1)) + T = apply(T, swapprime(dag(T), 0 => 1)) + + # Matrix test + sqrtM = map_eigvals(sqrt, M, [s1'], [dag(s1)]; ishermitian = true) + @test M ≈ apply(sqrtM, sqrtM) + + ## Tensor test + sqrtT = map_eigvals(sqrt, T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian = true) + @test T ≈ apply(sqrtT, sqrtT) + + # Permute and test again + T = permute(T, dag(s2), s2', dag(s1), s1') + sqrtT = map_eigvals(sqrt, T, [s1', s2'], [dag(s1), dag(s2)]; ishermitian = true) + @test T ≈ apply(sqrtT, sqrtT) + + ## Explicitly passing indices in different, valid orders + sqrtT = map_eigvals(sqrt, T, [s2', s1'], [dag(s2), dag(s1)]; ishermitian = true) + @test T ≈ apply(sqrtT, sqrtT) + sqrtT = map_eigvals(sqrt, T, [dag(s2), dag(s1)], [s2', s1'], ; ishermitian = true) + @test T ≈ apply(sqrtT, sqrtT) + sqrtT = map_eigvals(sqrt, T, [dag(s1), dag(s2)], [s1', s2'], ; ishermitian = true) + @test T ≈ apply(sqrtT, sqrtT) + + # Test bosonic index case while fermion system is enabled + b = Index([QN("Nb", 0) => 2, QN("Nb", 1) => 2]) + T = random_itensor(b', dag(b)) + T = apply(T, swapprime(dag(T), 0 => 1)) + sqrtT = map_eigvals(sqrt, T, [b'], [dag(b)]; ishermitian = true) + @test T ≈ apply(sqrtT, sqrtT) + + ITensors.disable_auto_fermion() + end end end diff --git a/test/test_normalize.jl b/test/test_normalize.jl index 873a2385..d7f975d3 100644 --- a/test/test_normalize.jl +++ b/test/test_normalize.jl @@ -1,15 +1,15 @@ @eval module $(gensym()) using ITensorNetworks: - BeliefPropagationCache, - QuadraticFormNetwork, - edge_scalars, - norm_sqr_network, - messages, - random_tensornetwork, - scalartype, - siteinds, - vertex_scalars, - rescale + BeliefPropagationCache, + QuadraticFormNetwork, + edge_scalars, + norm_sqr_network, + messages, + random_tensornetwork, + scalartype, + siteinds, + vertex_scalars, + rescale using ITensors: dag, inner, scalar using Graphs: SimpleGraph, uniform_tree using LinearAlgebra: normalize @@ -20,40 +20,40 @@ using TensorOperations: TensorOperations using Test: @test, @testset @testset "Normalize" begin - #First lets do a flat tree - nx, ny = 2, 3 - χ = 2 - rng = StableRNG(1234) - - g = named_comb_tree((nx, ny)) - tn = random_tensornetwork(rng, g; link_space=χ) - - tn_r = rescale(tn; alg="exact") - @test scalar(tn_r; alg="exact") ≈ 1.0 - - tn_r = rescale(tn; alg="bp", cache_update_kwargs=(; maxiter=20)) - @test scalar(tn_r; alg="exact") ≈ 1.0 - - #Now a state on a loopy graph - Lx, Ly = 3, 2 - χ = 2 - rng = StableRNG(1234) - - g = named_grid((Lx, Ly)) - s = siteinds("S=1/2", g) - x = random_tensornetwork(rng, ComplexF32, s; link_space=χ) - - ψ = normalize(x; alg="exact") - @test scalar(norm_sqr_network(ψ); alg="exact") ≈ 1.0 - - ψIψ_bpc = Ref(BeliefPropagationCache(QuadraticFormNetwork(x))) - ψ = normalize( - x; alg="bp", (cache!)=ψIψ_bpc, update_cache=true, cache_update_kwargs=(; maxiter=20) - ) - ψIψ_bpc = ψIψ_bpc[] - @test all(m -> scalartype(only(m)) == ComplexF32, messages(ψIψ_bpc)) - @test all(x -> x ≈ 1.0, edge_scalars(ψIψ_bpc)) - @test all(x -> x ≈ 1.0, vertex_scalars(ψIψ_bpc)) - @test scalar(QuadraticFormNetwork(ψ); alg="bp", cache_update_kwargs=(; maxiter=20)) ≈ 1.0 + #First lets do a flat tree + nx, ny = 2, 3 + χ = 2 + rng = StableRNG(1234) + + g = named_comb_tree((nx, ny)) + tn = random_tensornetwork(rng, g; link_space = χ) + + tn_r = rescale(tn; alg = "exact") + @test scalar(tn_r; alg = "exact") ≈ 1.0 + + tn_r = rescale(tn; alg = "bp", cache_update_kwargs = (; maxiter = 20)) + @test scalar(tn_r; alg = "exact") ≈ 1.0 + + #Now a state on a loopy graph + Lx, Ly = 3, 2 + χ = 2 + rng = StableRNG(1234) + + g = named_grid((Lx, Ly)) + s = siteinds("S=1/2", g) + x = random_tensornetwork(rng, ComplexF32, s; link_space = χ) + + ψ = normalize(x; alg = "exact") + @test scalar(norm_sqr_network(ψ); alg = "exact") ≈ 1.0 + + ψIψ_bpc = Ref(BeliefPropagationCache(QuadraticFormNetwork(x))) + ψ = normalize( + x; alg = "bp", (cache!) = ψIψ_bpc, update_cache = true, cache_update_kwargs = (; maxiter = 20) + ) + ψIψ_bpc = ψIψ_bpc[] + @test all(m -> scalartype(only(m)) == ComplexF32, messages(ψIψ_bpc)) + @test all(x -> x ≈ 1.0, edge_scalars(ψIψ_bpc)) + @test all(x -> x ≈ 1.0, vertex_scalars(ψIψ_bpc)) + @test scalar(QuadraticFormNetwork(ψ); alg = "bp", cache_update_kwargs = (; maxiter = 20)) ≈ 1.0 end end diff --git a/test/test_opsum_to_ttn.jl b/test/test_opsum_to_ttn.jl index 602dab73..455fcdfe 100644 --- a/test/test_opsum_to_ttn.jl +++ b/test/test_opsum_to_ttn.jl @@ -3,16 +3,16 @@ using DataGraphs: vertex_data using Dictionaries: Dictionary, getindices using Graphs: add_vertex!, rem_vertex!, add_edge!, rem_edge!, vertices using ITensors: - ITensors, - Index, - ITensor, - @disable_warn_order, - combinedind, - combiner, - contract, - dag, - inds, - removeqns + ITensors, + Index, + ITensor, + @disable_warn_order, + combinedind, + combiner, + contract, + dag, + inds, + removeqns using ITensorMPS: ITensorMPS using ITensors.NDTensors: matrix using ITensorNetworks: ITensorNetworks, OpSum, ttn, siteinds @@ -25,247 +25,247 @@ using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_grid using Test: @test, @test_broken, @testset function to_matrix(t::ITensor) - c = combiner(inds(t; plev=0)) - tc = (t * c) * dag(c') - cind = combinedind(c) - return matrix(tc, cind', cind) + c = combiner(inds(t; plev = 0)) + tc = (t * c) * dag(c') + cind = combinedind(c) + return matrix(tc, cind', cind) end @testset "OpSum to TTN converter" begin - @testset "OpSum to TTN" begin - # small comb tree - auto_fermion_enabled = ITensors.using_auto_fermion() - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - - is = siteinds("S=1/2", c) - - # linearized version - linear_order = [4, 1, 2, 5, 3, 6] - vmap = Dictionary(collect(vertices(is))[linear_order], eachindex(linear_order)) - sites = only.(collect(vertex_data(is)))[linear_order] - - # test with next-to-nearest-neighbor Ising Hamiltonian - J1 = -1 - J2 = 2 - h = 0.5 - H = ModelHamiltonians.ising(c; J1=J1, J2=J2, h=h) - # add combination of longer range interactions - Hlr = copy(H) - Hlr += 5, "Z", (1, 2), "Z", (2, 2), "Z", (3, 2) - Hlr += -4, "Z", (1, 1), "Z", (2, 2), "Z", (3, 1) - Hlr += 2.0, "Z", (2, 2), "Z", (3, 2) - Hlr += -1.0, "Z", (1, 2), "Z", (3, 1) - - # root_vertex = (1, 2) - # println(leaf_vertices(is)) - - @testset "Svd approach" for root_vertex in leaf_vertices(is) - # get TTN Hamiltonian directly - Hsvd = ttn(H, is; root_vertex, cutoff=1e-10) - # get corresponding MPO Hamiltonian - Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) - # compare resulting dense Hamiltonians - @disable_warn_order begin - Tttno = prod(Hline) - Tmpo = contract(Hsvd) - end - @test Tttno ≈ Tmpo rtol = 1e-6 - - Hsvd_lr = ttn(Hlr, is; root_vertex, cutoff=1e-10) - Hline_lr = ITensorMPS.MPO(replace_vertices(v -> vmap[v], Hlr), sites) - @disable_warn_order begin - Tttno_lr = prod(Hline_lr) - Tmpo_lr = contract(Hsvd_lr) - end - @test Tttno_lr ≈ Tmpo_lr rtol = 1e-6 + @testset "OpSum to TTN" begin + # small comb tree + auto_fermion_enabled = ITensors.using_auto_fermion() + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + + is = siteinds("S=1/2", c) + + # linearized version + linear_order = [4, 1, 2, 5, 3, 6] + vmap = Dictionary(collect(vertices(is))[linear_order], eachindex(linear_order)) + sites = only.(collect(vertex_data(is)))[linear_order] + + # test with next-to-nearest-neighbor Ising Hamiltonian + J1 = -1 + J2 = 2 + h = 0.5 + H = ModelHamiltonians.ising(c; J1 = J1, J2 = J2, h = h) + # add combination of longer range interactions + Hlr = copy(H) + Hlr += 5, "Z", (1, 2), "Z", (2, 2), "Z", (3, 2) + Hlr += -4, "Z", (1, 1), "Z", (2, 2), "Z", (3, 1) + Hlr += 2.0, "Z", (2, 2), "Z", (3, 2) + Hlr += -1.0, "Z", (1, 2), "Z", (3, 1) + + # root_vertex = (1, 2) + # println(leaf_vertices(is)) + + @testset "Svd approach" for root_vertex in leaf_vertices(is) + # get TTN Hamiltonian directly + Hsvd = ttn(H, is; root_vertex, cutoff = 1.0e-10) + # get corresponding MPO Hamiltonian + Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) + # compare resulting dense Hamiltonians + @disable_warn_order begin + Tttno = prod(Hline) + Tmpo = contract(Hsvd) + end + @test Tttno ≈ Tmpo rtol = 1.0e-6 + + Hsvd_lr = ttn(Hlr, is; root_vertex, cutoff = 1.0e-10) + Hline_lr = ITensorMPS.MPO(replace_vertices(v -> vmap[v], Hlr), sites) + @disable_warn_order begin + Tttno_lr = prod(Hline_lr) + Tmpo_lr = contract(Hsvd_lr) + end + @test Tttno_lr ≈ Tmpo_lr rtol = 1.0e-6 + end + if auto_fermion_enabled + ITensors.enable_auto_fermion() + end end - if auto_fermion_enabled - ITensors.enable_auto_fermion() - end - end - @testset "Multiple onsite terms (regression test for issue #62)" begin - auto_fermion_enabled = ITensors.using_auto_fermion() - if !auto_fermion_enabled - ITensors.enable_auto_fermion() - end - grid_dims = (2, 1) - g = named_grid(grid_dims) - s = siteinds("S=1/2", g) - - os1 = OpSum() - os1 += 1.0, "Sx", (1, 1) - os2 = OpSum() - os2 += 1.0, "Sy", (1, 1) - H1 = ttn(os1, s) - H2 = ttn(os2, s) - H3 = ttn(os1 + os2, s) - - @test H1 + H2 ≈ H3 rtol = 1e-6 - if auto_fermion_enabled - ITensors.enable_auto_fermion() - end - end - - @testset "OpSum to TTN QN" begin - # small comb tree - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - is = siteinds("S=1/2", c; conserve_qns=true) - is_noqns = copy(is) - for v in vertices(is) - is_noqns[v] = removeqns(is_noqns[v]) + @testset "Multiple onsite terms (regression test for issue #62)" begin + auto_fermion_enabled = ITensors.using_auto_fermion() + if !auto_fermion_enabled + ITensors.enable_auto_fermion() + end + grid_dims = (2, 1) + g = named_grid(grid_dims) + s = siteinds("S=1/2", g) + + os1 = OpSum() + os1 += 1.0, "Sx", (1, 1) + os2 = OpSum() + os2 += 1.0, "Sy", (1, 1) + H1 = ttn(os1, s) + H2 = ttn(os2, s) + H3 = ttn(os1 + os2, s) + + @test H1 + H2 ≈ H3 rtol = 1.0e-6 + if auto_fermion_enabled + ITensors.enable_auto_fermion() + end end - # linearized version - linear_order = [4, 1, 2, 5, 3, 6] - vmap = Dictionary(collect(vertices(is))[linear_order], eachindex(linear_order)) - sites = only.(collect(vertex_data(is)))[linear_order] - - # test with next-to-nearest-neighbor Ising Hamiltonian - J1 = -1 - J2 = 2 - h = 0.5 - H = ModelHamiltonians.heisenberg(c; J1=J1, J2=J2, h=h) - # add combination of longer range interactions - Hlr = copy(H) - Hlr += 5, "Z", (1, 2), "Z", (2, 2)#, "Z", (3,2) - Hlr += -4, "Z", (1, 1), "Z", (2, 2) - Hlr += 2.0, "Z", (2, 2), "Z", (3, 2) - Hlr += -1.0, "Z", (1, 2), "Z", (3, 1) - - # root_vertex = (1, 2) - # println(leaf_vertices(is)) - - @testset "Svd approach" for root_vertex in leaf_vertices(is) - # get TTN Hamiltonian directly - Hsvd = ttn(H, is; root_vertex, cutoff=1e-10) - # get corresponding MPO Hamiltonian - Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) - # compare resulting sparse Hamiltonians - - @disable_warn_order begin - Tmpo = prod(Hline) - Tttno = contract(Hsvd) - end - @test Tttno ≈ Tmpo rtol = 1e-6 - - Hsvd_lr = ttn(Hlr, is; root_vertex, cutoff=1e-10) - Hline_lr = ITensorMPS.MPO(replace_vertices(v -> vmap[v], Hlr), sites) - @disable_warn_order begin - Tttno_lr = prod(Hline_lr) - Tmpo_lr = contract(Hsvd_lr) - end - @test Tttno_lr ≈ Tmpo_lr rtol = 1e-6 + @testset "OpSum to TTN QN" begin + # small comb tree + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + is = siteinds("S=1/2", c; conserve_qns = true) + is_noqns = copy(is) + for v in vertices(is) + is_noqns[v] = removeqns(is_noqns[v]) + end + + # linearized version + linear_order = [4, 1, 2, 5, 3, 6] + vmap = Dictionary(collect(vertices(is))[linear_order], eachindex(linear_order)) + sites = only.(collect(vertex_data(is)))[linear_order] + + # test with next-to-nearest-neighbor Ising Hamiltonian + J1 = -1 + J2 = 2 + h = 0.5 + H = ModelHamiltonians.heisenberg(c; J1 = J1, J2 = J2, h = h) + # add combination of longer range interactions + Hlr = copy(H) + Hlr += 5, "Z", (1, 2), "Z", (2, 2) #, "Z", (3,2) + Hlr += -4, "Z", (1, 1), "Z", (2, 2) + Hlr += 2.0, "Z", (2, 2), "Z", (3, 2) + Hlr += -1.0, "Z", (1, 2), "Z", (3, 1) + + # root_vertex = (1, 2) + # println(leaf_vertices(is)) + + @testset "Svd approach" for root_vertex in leaf_vertices(is) + # get TTN Hamiltonian directly + Hsvd = ttn(H, is; root_vertex, cutoff = 1.0e-10) + # get corresponding MPO Hamiltonian + Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) + # compare resulting sparse Hamiltonians + + @disable_warn_order begin + Tmpo = prod(Hline) + Tttno = contract(Hsvd) + end + @test Tttno ≈ Tmpo rtol = 1.0e-6 + + Hsvd_lr = ttn(Hlr, is; root_vertex, cutoff = 1.0e-10) + Hline_lr = ITensorMPS.MPO(replace_vertices(v -> vmap[v], Hlr), sites) + @disable_warn_order begin + Tttno_lr = prod(Hline_lr) + Tmpo_lr = contract(Hsvd_lr) + end + @test Tttno_lr ≈ Tmpo_lr rtol = 1.0e-6 + end end - end - @testset "OpSum to TTN Fermions" begin - # small comb tree - auto_fermion_enabled = ITensors.using_auto_fermion() - if !auto_fermion_enabled - ITensors.enable_auto_fermion() + @testset "OpSum to TTN Fermions" begin + # small comb tree + auto_fermion_enabled = ITensors.using_auto_fermion() + if !auto_fermion_enabled + ITensors.enable_auto_fermion() + end + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + is = siteinds("Fermion", c; conserve_nf = true) + + # test with next-nearest neighbor tight-binding model + t = 1.0 + tp = 0.4 + U = 0.0 + h = 0.5 + H = ModelHamiltonians.tight_binding(c; t, tp, h) + + # add combination of longer range interactions + Hlr = copy(H) + + @testset "Svd approach" for root_vertex in leaf_vertices(is) + # get TTN Hamiltonian directly + Hsvd = ttn(H, is; root_vertex, cutoff = 1.0e-10) + # get corresponding MPO Hamiltonian + sites = [only(is[v]) for v in reverse(post_order_dfs_vertices(c, root_vertex))] + vmap = Dictionary(reverse(post_order_dfs_vertices(c, root_vertex)), 1:length(sites)) + Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) + @disable_warn_order begin + Tmpo = prod(Hline) + Tttno = contract(Hsvd) + end + + # verify that the norm isn't 0 and thus the same (which would indicate a problem with the autofermion system + @test norm(Tmpo) > 0 + @test norm(Tttno) > 0 + @test norm(Tmpo) ≈ norm(Tttno) rtol = 1.0e-6 + + # TODO: fix comparison for fermionic tensors + @test_broken Tmpo ≈ Tttno + # In the meantime: matricize tensors and convert to dense Matrix to compare element by element + dTmm = to_matrix(Tmpo) + dTtm = to_matrix(Tttno) + @test any(>(1.0e-14), dTmm - dTtm) + end + if !auto_fermion_enabled + ITensors.disable_auto_fermion() + end end - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - is = siteinds("Fermion", c; conserve_nf=true) - - # test with next-nearest neighbor tight-binding model - t = 1.0 - tp = 0.4 - U = 0.0 - h = 0.5 - H = ModelHamiltonians.tight_binding(c; t, tp, h) - - # add combination of longer range interactions - Hlr = copy(H) - - @testset "Svd approach" for root_vertex in leaf_vertices(is) - # get TTN Hamiltonian directly - Hsvd = ttn(H, is; root_vertex, cutoff=1e-10) - # get corresponding MPO Hamiltonian - sites = [only(is[v]) for v in reverse(post_order_dfs_vertices(c, root_vertex))] - vmap = Dictionary(reverse(post_order_dfs_vertices(c, root_vertex)), 1:length(sites)) - Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) - @disable_warn_order begin - Tmpo = prod(Hline) - Tttno = contract(Hsvd) - end - - # verify that the norm isn't 0 and thus the same (which would indicate a problem with the autofermion system - @test norm(Tmpo) > 0 - @test norm(Tttno) > 0 - @test norm(Tmpo) ≈ norm(Tttno) rtol = 1e-6 - - # TODO: fix comparison for fermionic tensors - @test_broken Tmpo ≈ Tttno - # In the meantime: matricize tensors and convert to dense Matrix to compare element by element - dTmm = to_matrix(Tmpo) - dTtm = to_matrix(Tttno) - @test any(>(1e-14), dTmm - dTtm) - end - if !auto_fermion_enabled - ITensors.disable_auto_fermion() - end - end - - @testset "OpSum to TTN QN missing" begin - # small comb tree - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - c2 = copy(c) - ## add an internal vertex into the comb graph c2 - add_vertex!(c2, (-1, 1)) - add_edge!(c2, (-1, 1) => (2, 2)) - add_edge!(c2, (-1, 1) => (3, 1)) - add_edge!(c2, (-1, 1) => (2, 1)) - rem_edge!(c2, (2, 1) => (2, 2)) - rem_edge!(c2, (2, 1) => (3, 1)) - - is = siteinds("S=1/2", c; conserve_qns=true) - is_missing_site = siteinds("S=1/2", c2; conserve_qns=true) - is_missing_site[(-1, 1)] = Vector{Index}[] - - # linearized version - linear_order = [4, 1, 2, 5, 3, 6] - vmap = Dictionary(collect(vertices(is))[linear_order], eachindex(linear_order)) - sites = only.(filter(d -> !isempty(d), collect(vertex_data(is_missing_site))))[linear_order] - - J1 = -1 - J2 = 2 - h = 0.5 - # connectivity of the Hamiltonian is that of the original comb graph - H = ModelHamiltonians.heisenberg(c; J1, J2, h) - - # add combination of longer range interactions - Hlr = copy(H) - Hlr += 5, "Z", (1, 2), "Z", (2, 2) - Hlr += -4, "Z", (1, 1), "Z", (2, 2) - Hlr += 2.0, "Z", (2, 2), "Z", (3, 2) - Hlr += -1.0, "Z", (1, 2), "Z", (3, 1) - - @testset "Svd approach" for root_vertex in leaf_vertices(is) - # get TTN Hamiltonian directly - Hsvd = ttn(H, is_missing_site; root_vertex, cutoff=1e-10) - # get corresponding MPO Hamiltonian - Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) - - # compare resulting sparse Hamiltonians - @disable_warn_order begin - Tmpo = prod(Hline) - Tttno = contract(Hsvd) - end - @test Tttno ≈ Tmpo rtol = 1e-6 - - Hsvd_lr = ttn(Hlr, is_missing_site; root_vertex, cutoff=1e-10) - Hline_lr = ITensorMPS.MPO(replace_vertices(v -> vmap[v], Hlr), sites) - @disable_warn_order begin - Tttno_lr = prod(Hline_lr) - Tmpo_lr = contract(Hsvd_lr) - end - @test Tttno_lr ≈ Tmpo_lr rtol = 1e-6 + + @testset "OpSum to TTN QN missing" begin + # small comb tree + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + c2 = copy(c) + ## add an internal vertex into the comb graph c2 + add_vertex!(c2, (-1, 1)) + add_edge!(c2, (-1, 1) => (2, 2)) + add_edge!(c2, (-1, 1) => (3, 1)) + add_edge!(c2, (-1, 1) => (2, 1)) + rem_edge!(c2, (2, 1) => (2, 2)) + rem_edge!(c2, (2, 1) => (3, 1)) + + is = siteinds("S=1/2", c; conserve_qns = true) + is_missing_site = siteinds("S=1/2", c2; conserve_qns = true) + is_missing_site[(-1, 1)] = Vector{Index}[] + + # linearized version + linear_order = [4, 1, 2, 5, 3, 6] + vmap = Dictionary(collect(vertices(is))[linear_order], eachindex(linear_order)) + sites = only.(filter(d -> !isempty(d), collect(vertex_data(is_missing_site))))[linear_order] + + J1 = -1 + J2 = 2 + h = 0.5 + # connectivity of the Hamiltonian is that of the original comb graph + H = ModelHamiltonians.heisenberg(c; J1, J2, h) + + # add combination of longer range interactions + Hlr = copy(H) + Hlr += 5, "Z", (1, 2), "Z", (2, 2) + Hlr += -4, "Z", (1, 1), "Z", (2, 2) + Hlr += 2.0, "Z", (2, 2), "Z", (3, 2) + Hlr += -1.0, "Z", (1, 2), "Z", (3, 1) + + @testset "Svd approach" for root_vertex in leaf_vertices(is) + # get TTN Hamiltonian directly + Hsvd = ttn(H, is_missing_site; root_vertex, cutoff = 1.0e-10) + # get corresponding MPO Hamiltonian + Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) + + # compare resulting sparse Hamiltonians + @disable_warn_order begin + Tmpo = prod(Hline) + Tttno = contract(Hsvd) + end + @test Tttno ≈ Tmpo rtol = 1.0e-6 + + Hsvd_lr = ttn(Hlr, is_missing_site; root_vertex, cutoff = 1.0e-10) + Hline_lr = ITensorMPS.MPO(replace_vertices(v -> vmap[v], Hlr), sites) + @disable_warn_order begin + Tttno_lr = prod(Hline_lr) + Tmpo_lr = contract(Hsvd_lr) + end + @test Tttno_lr ≈ Tmpo_lr rtol = 1.0e-6 + end end - end end end diff --git a/test/test_sitetype.jl b/test/test_sitetype.jl index 33283e91..9405ae99 100644 --- a/test/test_sitetype.jl +++ b/test/test_sitetype.jl @@ -9,56 +9,56 @@ using NamedGraphs.NamedGraphGenerators: named_grid using Test: @test, @testset @testset "Site ind system" begin - g = named_grid((2, 2)) - sitetypes = [rand(["S=1/2", "S=1", "Boson", "Fermion"]) for _ in 1:nv(g)] - dims = space.(SiteType.(sitetypes)) - typemap = Dictionary(vertices(g), sitetypes) - dimmap = Dictionary(vertices(g), dims) - ftype(v::Tuple) = iseven(sum(isodd.(v))) ? "S=1/2" : "S=1" - fdim(v::Tuple) = space(SiteType(ftype(v))) - testtag = "TestTag" + g = named_grid((2, 2)) + sitetypes = [rand(["S=1/2", "S=1", "Boson", "Fermion"]) for _ in 1:nv(g)] + dims = space.(SiteType.(sitetypes)) + typemap = Dictionary(vertices(g), sitetypes) + dimmap = Dictionary(vertices(g), dims) + ftype(v::Tuple) = iseven(sum(isodd.(v))) ? "S=1/2" : "S=1" + fdim(v::Tuple) = space(SiteType(ftype(v))) + testtag = "TestTag" - d1 = map(v -> Index(2), vertices(g)) - d2 = map(v -> "S=1/2", vertices(g)) - for x in (v -> d1[v], d1, v -> d2[v], d2) - s = siteinds(x, g) - @test s[1, 1] isa Vector{<:Index} - @test s[1, 2] isa Vector{<:Index} - @test s[2, 1] isa Vector{<:Index} - @test s[2, 2] isa Vector{<:Index} - end + d1 = map(v -> Index(2), vertices(g)) + d2 = map(v -> "S=1/2", vertices(g)) + for x in (v -> d1[v], d1, v -> d2[v], d2) + s = siteinds(x, g) + @test s[1, 1] isa Vector{<:Index} + @test s[1, 2] isa Vector{<:Index} + @test s[2, 1] isa Vector{<:Index} + @test s[2, 2] isa Vector{<:Index} + end - # uniform string sitetype - s_us = siteinds(sitetypes[1], g; addtags=testtag) - @test s_us isa IndsNetwork - @test all(dim.(vertex_data(s_us)) .== dims[1]) - @test all(hastags.(vertex_data(s_us), Ref("$testtag,$(sitetypes[1]),Site"))) - # dictionary string sitetype - s_ds = siteinds(typemap, g; addtags=testtag) - @test s_ds isa IndsNetwork - @test all(dim(only(s_ds[v])) == dimmap[v] for v in vertices(g)) - @test all(hastags(only(s_ds[v]), "$testtag,$(typemap[v]),Site") for v in vertices(g)) + # uniform string sitetype + s_us = siteinds(sitetypes[1], g; addtags = testtag) + @test s_us isa IndsNetwork + @test all(dim.(vertex_data(s_us)) .== dims[1]) + @test all(hastags.(vertex_data(s_us), Ref("$testtag,$(sitetypes[1]),Site"))) + # dictionary string sitetype + s_ds = siteinds(typemap, g; addtags = testtag) + @test s_ds isa IndsNetwork + @test all(dim(only(s_ds[v])) == dimmap[v] for v in vertices(g)) + @test all(hastags(only(s_ds[v]), "$testtag,$(typemap[v]),Site") for v in vertices(g)) - # uniform integer sitetype - s_ui = siteinds(dims[1], g; addtags=testtag) - @test s_ui isa IndsNetwork - @test all(dim.(vertex_data(s_ui)) .== dims[1]) - @test all(hastags.(vertex_data(s_ui), Ref("$testtag,Site"))) - # dictionary integer sitetype - s_di = siteinds(dimmap, g; addtags=testtag) - @test s_di isa IndsNetwork - @test all(dim(only(s_di[v])) == dimmap[v] for v in vertices(g)) - @test all(hastags.(vertex_data(s_di), Ref("$testtag,Site"))) + # uniform integer sitetype + s_ui = siteinds(dims[1], g; addtags = testtag) + @test s_ui isa IndsNetwork + @test all(dim.(vertex_data(s_ui)) .== dims[1]) + @test all(hastags.(vertex_data(s_ui), Ref("$testtag,Site"))) + # dictionary integer sitetype + s_di = siteinds(dimmap, g; addtags = testtag) + @test s_di isa IndsNetwork + @test all(dim(only(s_di[v])) == dimmap[v] for v in vertices(g)) + @test all(hastags.(vertex_data(s_di), Ref("$testtag,Site"))) - # function string site type - s_fs = siteinds(ftype, g; addtags=testtag) - @test s_fs isa IndsNetwork - @test all(dim(only(s_fs[v])) == fdim(v) for v in vertices(g)) - @test all(hastags(only(s_fs[v]), "$testtag,$(ftype(v)),Site") for v in vertices(g)) - # function integer site type - s_fi = siteinds(fdim, g; addtags=testtag) - @test s_fs isa IndsNetwork - @test all(dim(only(s_fs[v])) == fdim(v) for v in vertices(g)) - @test all(hastags.(vertex_data(s_fs), Ref("$testtag,Site"))) + # function string site type + s_fs = siteinds(ftype, g; addtags = testtag) + @test s_fs isa IndsNetwork + @test all(dim(only(s_fs[v])) == fdim(v) for v in vertices(g)) + @test all(hastags(only(s_fs[v]), "$testtag,$(ftype(v)),Site") for v in vertices(g)) + # function integer site type + s_fi = siteinds(fdim, g; addtags = testtag) + @test s_fs isa IndsNetwork + @test all(dim(only(s_fs[v])) == fdim(v) for v in vertices(g)) + @test all(hastags.(vertex_data(s_fs), Ref("$testtag,Site"))) end end diff --git a/test/test_tebd.jl b/test/test_tebd.jl index 71acd1ed..0c041a4c 100644 --- a/test/test_tebd.jl +++ b/test/test_tebd.jl @@ -11,47 +11,47 @@ using Test: @test, @testset, @test_broken ITensors.disable_warn_order() @testset "Ising TEBD" begin - dims = (2, 3) - n = prod(dims) - g = named_grid(dims) + dims = (2, 3) + n = prod(dims) + g = named_grid(dims) - h = 0.1 + h = 0.1 - s = siteinds("S=1/2", g) + s = siteinds("S=1/2", g) - # - # PEPS TEBD optimization - # - ℋ = ModelHamiltonians.ising(g; h) - χ = 2 - β = 2.0 - Δβ = 0.2 + # + # PEPS TEBD optimization + # + ℋ = ModelHamiltonians.ising(g; h) + χ = 2 + β = 2.0 + Δβ = 0.2 - ψ_init = ITensorNetwork(v -> "↑", s) - #E0 = expect(ℋ, ψ_init) - ψ = tebd( - group_terms(ℋ, g), - ψ_init; - β, - Δβ, - cutoff=1e-8, - maxdim=χ, - ortho=false, - print_frequency=typemax(Int), - ) - #E1 = expect(ℋ, ψ) - ψ = tebd( - group_terms(ℋ, g), - ψ_init; - β, - Δβ, - cutoff=1e-8, - maxdim=χ, - ortho=true, - print_frequency=typemax(Int), - ) - #E2 = expect(ℋ, ψ) - #@show E0, E1, E2, E_dmrg - @test_broken (((abs((E2 - E1) / E2) < 1e-3) && (E1 < E0)) || (E2 < E1 < E0)) + ψ_init = ITensorNetwork(v -> "↑", s) + #E0 = expect(ℋ, ψ_init) + ψ = tebd( + group_terms(ℋ, g), + ψ_init; + β, + Δβ, + cutoff = 1.0e-8, + maxdim = χ, + ortho = false, + print_frequency = typemax(Int), + ) + #E1 = expect(ℋ, ψ) + ψ = tebd( + group_terms(ℋ, g), + ψ_init; + β, + Δβ, + cutoff = 1.0e-8, + maxdim = χ, + ortho = true, + print_frequency = typemax(Int), + ) + #E2 = expect(ℋ, ψ) + #@show E0, E1, E2, E_dmrg + @test_broken (((abs((E2 - E1) / E2) < 1.0e-3) && (E1 < E0)) || (E2 < E1 < E0)) end end diff --git a/test/test_ttn_contract.jl b/test/test_ttn_contract.jl index 500826c3..569cd2bc 100644 --- a/test/test_ttn_contract.jl +++ b/test/test_ttn_contract.jl @@ -1,20 +1,20 @@ @eval module $(gensym()) using Graphs: vertices using ITensorNetworks: - ITensorNetworks, - OpSum, - ProjOuterProdTTN, - ProjTTNSum, - ttn, - apply, - contract, - delta, - dmrg, - inner, - mpo, - random_mps, - random_ttn, - siteinds + ITensorNetworks, + OpSum, + ProjOuterProdTTN, + ProjTTNSum, + ttn, + apply, + contract, + delta, + dmrg, + inner, + mpo, + random_mps, + random_ttn, + siteinds using ITensorNetworks.ModelHamiltonians: ModelHamiltonians using ITensors: prime, replaceinds, replaceprime using LinearAlgebra: norm, normalize @@ -23,130 +23,130 @@ using StableRNGs: StableRNG using Test: @test, @test_broken, @testset @testset "Contract MPO" begin - N = 20 - s = siteinds("S=1/2", N) - rng = StableRNG(1234) - psi = random_mps(rng, s; link_space=8) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - for j in 1:(N - 2) - os += 0.5, "S+", j, "S-", j + 2 - os += 0.5, "S-", j, "S+", j + 2 - os += "Sz", j, "Sz", j + 2 - end - H = mpo(os, s) + N = 20 + s = siteinds("S=1/2", N) + rng = StableRNG(1234) + psi = random_mps(rng, s; link_space = 8) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + for j in 1:(N - 2) + os += 0.5, "S+", j, "S-", j + 2 + os += 0.5, "S-", j, "S+", j + 2 + os += "Sz", j, "Sz", j + 2 + end + H = mpo(os, s) - # Test basic usage with default parameters - Hpsi = apply(H, psi; alg="fit", init=psi, nsweeps=1) - @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 1e-5 - # Test variational compression via DMRG - Hfit = ProjOuterProdTTN(psi', H) - e, Hpsi_via_dmrg = dmrg(Hfit, psi; updater_kwargs=(; which_eigval=:LR,), nsweeps=1) - @test abs(inner(Hpsi_via_dmrg, Hpsi / norm(Hpsi))) ≈ 1 rtol = 1e-4 - # Test whether the interface works for ProjTTNSum with factors - Hfit = ProjTTNSum([ProjOuterProdTTN(psi', H), ProjOuterProdTTN(psi', H)], [-0.2, -0.8]) - e, Hpsi_via_dmrg = dmrg(Hfit, psi; nsweeps=1, updater_kwargs=(; which_eigval=:SR,)) - @test abs(inner(Hpsi_via_dmrg, Hpsi / norm(Hpsi))) ≈ 1 rtol = 1e-4 + # Test basic usage with default parameters + Hpsi = apply(H, psi; alg = "fit", init = psi, nsweeps = 1) + @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 1.0e-5 + # Test variational compression via DMRG + Hfit = ProjOuterProdTTN(psi', H) + e, Hpsi_via_dmrg = dmrg(Hfit, psi; updater_kwargs = (; which_eigval = :LR), nsweeps = 1) + @test abs(inner(Hpsi_via_dmrg, Hpsi / norm(Hpsi))) ≈ 1 rtol = 1.0e-4 + # Test whether the interface works for ProjTTNSum with factors + Hfit = ProjTTNSum([ProjOuterProdTTN(psi', H), ProjOuterProdTTN(psi', H)], [-0.2, -0.8]) + e, Hpsi_via_dmrg = dmrg(Hfit, psi; nsweeps = 1, updater_kwargs = (; which_eigval = :SR)) + @test abs(inner(Hpsi_via_dmrg, Hpsi / norm(Hpsi))) ≈ 1 rtol = 1.0e-4 - # Test basic usage for use with multiple ProjOuterProdTTN with default parameters - # BLAS.axpy-like test - os_id = OpSum() - os_id += -1, "Id", 1, "Id", 2 - minus_identity = mpo(os_id, s) - os_id = OpSum() - os_id += +1, "Id", 1, "Id", 2 - identity = mpo(os_id, s) - Hpsi = ITensorNetworks.sum_apply( - [(H, psi), (minus_identity, psi)]; alg="fit", init=psi, nsweeps=3 - ) - @test inner(psi, Hpsi) ≈ (inner(psi', H, psi) - norm(psi)^2) rtol = 1e-5 - # Test the above via DMRG - # ToDo: Investigate why this is broken - Hfit = ProjTTNSum([ProjOuterProdTTN(psi', H), ProjOuterProdTTN(psi', identity)], [-1, 1]) - e, Hpsi_normalized = dmrg(Hfit, psi; nsweeps=3, updater_kwargs=(; which_eigval=:SR)) - @test_broken abs(inner(Hpsi, (Hpsi_normalized) / norm(Hpsi))) ≈ 1 rtol = 1e-5 + # Test basic usage for use with multiple ProjOuterProdTTN with default parameters + # BLAS.axpy-like test + os_id = OpSum() + os_id += -1, "Id", 1, "Id", 2 + minus_identity = mpo(os_id, s) + os_id = OpSum() + os_id += +1, "Id", 1, "Id", 2 + identity = mpo(os_id, s) + Hpsi = ITensorNetworks.sum_apply( + [(H, psi), (minus_identity, psi)]; alg = "fit", init = psi, nsweeps = 3 + ) + @test inner(psi, Hpsi) ≈ (inner(psi', H, psi) - norm(psi)^2) rtol = 1.0e-5 + # Test the above via DMRG + # ToDo: Investigate why this is broken + Hfit = ProjTTNSum([ProjOuterProdTTN(psi', H), ProjOuterProdTTN(psi', identity)], [-1, 1]) + e, Hpsi_normalized = dmrg(Hfit, psi; nsweeps = 3, updater_kwargs = (; which_eigval = :SR)) + @test_broken abs(inner(Hpsi, (Hpsi_normalized) / norm(Hpsi))) ≈ 1 rtol = 1.0e-5 - # - # Change "top" indices of MPO to be a different set - # - t = siteinds("S=1/2", N) - psit = deepcopy(psi) + # + # Change "top" indices of MPO to be a different set + # + t = siteinds("S=1/2", N) + psit = deepcopy(psi) - for j in 1:N - H[j] *= delta(s[j]', t[j]) - psit[j] *= delta(s[j], t[j]) - end - # Test with nsweeps=3 - Hpsi = contract(H, psi; alg="fit", init=psit, nsweeps=3) - @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1e-5 - # Test with less good initial guess MPS not equal to psi - psi_guess = truncate(psit; maxdim=2) - Hpsi = contract(H, psi; alg="fit", nsweeps=4, init=psi_guess) - @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1e-5 + for j in 1:N + H[j] *= delta(s[j]', t[j]) + psit[j] *= delta(s[j], t[j]) + end + # Test with nsweeps=3 + Hpsi = contract(H, psi; alg = "fit", init = psit, nsweeps = 3) + @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1.0e-5 + # Test with less good initial guess MPS not equal to psi + psi_guess = truncate(psit; maxdim = 2) + Hpsi = contract(H, psi; alg = "fit", nsweeps = 4, init = psi_guess) + @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1.0e-5 - # Test with nsite=1 - rng = StableRNG(1234) - Hpsi_guess = random_mps(rng, t; link_space=32) - Hpsi = contract(H, psi; alg="fit", init=Hpsi_guess, nsites=1, nsweeps=4) - @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1e-4 + # Test with nsite=1 + rng = StableRNG(1234) + Hpsi_guess = random_mps(rng, t; link_space = 32) + Hpsi = contract(H, psi; alg = "fit", init = Hpsi_guess, nsites = 1, nsweeps = 4) + @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1.0e-4 end @testset "Contract TTN" begin - tooth_lengths = fill(4, 4) - root_vertex = (1, 4) - c = named_comb_tree(tooth_lengths) + tooth_lengths = fill(4, 4) + root_vertex = (1, 4) + c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - rng = StableRNG(1234) - psi = normalize(random_ttn(rng, s; link_space=8)) + s = siteinds("S=1/2", c) + rng = StableRNG(1234) + psi = normalize(random_ttn(rng, s; link_space = 8)) - os = ModelHamiltonians.heisenberg(c; J1=1, J2=1) - H = ttn(os, s) + os = ModelHamiltonians.heisenberg(c; J1 = 1, J2 = 1) + H = ttn(os, s) - # Test basic usage with default parameters - Hpsi = apply(H, psi; alg="fit", init=psi, nsweeps=1, cutoff=eps()) - @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 1e-5 - # Test usage with non-default parameters - Hpsi = apply( - H, psi; alg="fit", init=psi, nsweeps=5, maxdim=[16, 32], cutoff=[1e-4, 1e-8, 1e-12] - ) - @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 1e-2 + # Test basic usage with default parameters + Hpsi = apply(H, psi; alg = "fit", init = psi, nsweeps = 1, cutoff = eps()) + @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 1.0e-5 + # Test usage with non-default parameters + Hpsi = apply( + H, psi; alg = "fit", init = psi, nsweeps = 5, maxdim = [16, 32], cutoff = [1.0e-4, 1.0e-8, 1.0e-12] + ) + @test inner(psi, Hpsi) ≈ inner(psi', H, psi) rtol = 1.0e-2 - # Test basic usage for multiple ProjOuterProdTTN with default parameters - # BLAS.axpy-like test - os_id = OpSum() - os_id += -1, "Id", first(vertices(s)), "Id", first(vertices(s)) - minus_identity = ttn(os_id, s) - Hpsi = ITensorNetworks.sum_apply( - [(H, psi), (minus_identity, psi)]; alg="fit", init=psi, nsweeps=1 - ) - @test inner(psi, Hpsi) ≈ (inner(psi', H, psi) - norm(psi)^2) rtol = 1e-5 + # Test basic usage for multiple ProjOuterProdTTN with default parameters + # BLAS.axpy-like test + os_id = OpSum() + os_id += -1, "Id", first(vertices(s)), "Id", first(vertices(s)) + minus_identity = ttn(os_id, s) + Hpsi = ITensorNetworks.sum_apply( + [(H, psi), (minus_identity, psi)]; alg = "fit", init = psi, nsweeps = 1 + ) + @test inner(psi, Hpsi) ≈ (inner(psi', H, psi) - norm(psi)^2) rtol = 1.0e-5 - # - # Change "top" indices of TTN to be a different set - # - t = siteinds("S=1/2", c) - psit = deepcopy(psi) - psit = replaceinds(psit, s => t) - H = replaceinds(H, prime(s; links=[]) => t) + # + # Change "top" indices of TTN to be a different set + # + t = siteinds("S=1/2", c) + psit = deepcopy(psi) + psit = replaceinds(psit, s => t) + H = replaceinds(H, prime(s; links = []) => t) - # Test with nsweeps=2 - Hpsi = contract(H, psi; alg="fit", init=psit, nsweeps=2) - @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1e-5 + # Test with nsweeps=2 + Hpsi = contract(H, psi; alg = "fit", init = psit, nsweeps = 2) + @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1.0e-5 - # Test with less good initial guess MPS not equal to psi - Hpsi_guess = truncate(psit; maxdim=2) - Hpsi = contract(H, psi; alg="fit", nsweeps=4, init=Hpsi_guess) - @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1e-5 + # Test with less good initial guess MPS not equal to psi + Hpsi_guess = truncate(psit; maxdim = 2) + Hpsi = contract(H, psi; alg = "fit", nsweeps = 4, init = Hpsi_guess) + @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1.0e-5 - # Test with nsite=1 - rng = StableRNG(1234) - Hpsi_guess = random_ttn(rng, t; link_space=32) - Hpsi = contract(H, psi; alg="fit", nsites=1, nsweeps=10, init=Hpsi_guess) - @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1e-2 + # Test with nsite=1 + rng = StableRNG(1234) + Hpsi_guess = random_ttn(rng, t; link_space = 32) + Hpsi = contract(H, psi; alg = "fit", nsites = 1, nsweeps = 10, init = Hpsi_guess) + @test inner(psit, Hpsi) ≈ inner(psit, H, psi) rtol = 1.0e-2 end end diff --git a/test/test_ttn_dmrg.jl b/test/test_ttn_dmrg.jl index b8a8cdb8..577a5875 100644 --- a/test/test_ttn_dmrg.jl +++ b/test/test_ttn_dmrg.jl @@ -4,17 +4,17 @@ using Dictionaries: Dictionary using Graphs: nv, vertices, uniform_tree using ITensorMPS: ITensorMPS using ITensorNetworks: - ITensorNetworks, - OpSum, - ttn, - apply, - dmrg, - inner, - mpo, - random_mps, - random_ttn, - linkdims, - siteinds + ITensorNetworks, + OpSum, + ttn, + apply, + dmrg, + inner, + mpo, + random_mps, + random_ttn, + linkdims, + siteinds using ITensorNetworks.ITensorsExtensions: replace_vertices using ITensorNetworks.ModelHamiltonians: ModelHamiltonians using ITensors: ITensors @@ -32,46 +32,46 @@ using Test: @test, @test_broken, @testset ITensors.disable_auto_fermion() @testset "MPS DMRG" for nsites in [1, 2] - N = 10 - cutoff = 1e-12 - - s = siteinds("S=1/2", N) - - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - - H = mpo(os, s) - - rng = StableRNG(1234) - psi = random_mps(rng, s; link_space=20) - - nsweeps = 10 - maxdim = [10, 20, 40, 100] - - # Compare to `ITensors.MPO` version of `dmrg` - H_mpo = ITensorMPS.MPO([H[v] for v in 1:nv(H)]) - psi_mps = ITensorMPS.MPS([psi[v] for v in 1:nv(psi)]) - e2, psi2 = ITensorMPS.dmrg(H_mpo, psi_mps; nsweeps, maxdim, outputlevel=0) - - e, psi = dmrg( - H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs=(; krylovdim=3, maxiter=1) - ) - @test inner(psi', H, psi) ≈ e - @test inner(psi', H, psi) ≈ inner(psi2', H_mpo, psi2) - - # Alias for `ITensorNetworks.dmrg` - e, psi = eigsolve( - H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs=(; krylovdim=3, maxiter=1) - ) - @test inner(psi', H, psi) ≈ e - @test inner(psi', H, psi) ≈ inner(psi2', H_mpo, psi2) - - # Test custom sweep regions #BROKEN, ToDo: Make proper custom sweep regions for test - #= + N = 10 + cutoff = 1.0e-12 + + s = siteinds("S=1/2", N) + + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + + H = mpo(os, s) + + rng = StableRNG(1234) + psi = random_mps(rng, s; link_space = 20) + + nsweeps = 10 + maxdim = [10, 20, 40, 100] + + # Compare to `ITensors.MPO` version of `dmrg` + H_mpo = ITensorMPS.MPO([H[v] for v in 1:nv(H)]) + psi_mps = ITensorMPS.MPS([psi[v] for v in 1:nv(psi)]) + e2, psi2 = ITensorMPS.dmrg(H_mpo, psi_mps; nsweeps, maxdim, outputlevel = 0) + + e, psi = dmrg( + H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs = (; krylovdim = 3, maxiter = 1) + ) + @test inner(psi', H, psi) ≈ e + @test inner(psi', H, psi) ≈ inner(psi2', H_mpo, psi2) + + # Alias for `ITensorNetworks.dmrg` + e, psi = eigsolve( + H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs = (; krylovdim = 3, maxiter = 1) + ) + @test inner(psi', H, psi) ≈ e + @test inner(psi', H, psi) ≈ inner(psi2', H_mpo, psi2) + + # Test custom sweep regions #BROKEN, ToDo: Make proper custom sweep regions for test + #= orig_E = inner(psi', H, psi) sweep_regions = [[1], [2], [3], [3], [2], [1]] e, psi = dmrg(H, psi; nsweeps, maxdim, cutoff, sweep_regions) @@ -79,250 +79,250 @@ ITensors.disable_auto_fermion() @test new_E ≈ orig_E =# - # - # Test outputlevels are working - # - prev_output = "" - for outputlevel in 0:2 - output = @capture_out begin - e, psi = dmrg( - H, - psi; - outputlevel, - nsweeps, - maxdim, - cutoff, - nsites, - updater_kwargs=(; krylovdim=3, maxiter=1), - ) - end - if outputlevel == 0 - @test length(output) == 0 - else - @test length(output) > length(prev_output) + # + # Test outputlevels are working + # + prev_output = "" + for outputlevel in 0:2 + output = @capture_out begin + e, psi = dmrg( + H, + psi; + outputlevel, + nsweeps, + maxdim, + cutoff, + nsites, + updater_kwargs = (; krylovdim = 3, maxiter = 1), + ) + end + if outputlevel == 0 + @test length(output) == 0 + else + @test length(output) > length(prev_output) + end + prev_output = output end - prev_output = output - end end @testset "Observers" begin - N = 10 - cutoff = 1e-12 - s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = mpo(os, s) - rng = StableRNG(1234) - psi = random_mps(rng, s; link_space=20) - - nsweeps = 4 - maxdim = [20, 40, 80, 80] - cutoff = [1e-10] - - # - # Make observers - # - sweep(; which_sweep, kw...) = which_sweep - sweep_observer! = observer(sweep) - - region(; which_region_update, sweep_plan, kw...) = first(sweep_plan[which_region_update]) - energy(; eigvals, kw...) = eigvals[1] - region_observer! = observer(region, sweep, energy) - - e, psi = dmrg(H, psi; nsweeps, maxdim, cutoff, sweep_observer!, region_observer!) - - # - # Test out certain values - # - @test region_observer![9, :region] == [2, 1] - @test region_observer![30, :energy] < -4.25 - @test region_observer![30, :energy] ≈ e rtol = 1e-6 + N = 10 + cutoff = 1.0e-12 + s = siteinds("S=1/2", N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = mpo(os, s) + rng = StableRNG(1234) + psi = random_mps(rng, s; link_space = 20) + + nsweeps = 4 + maxdim = [20, 40, 80, 80] + cutoff = [1.0e-10] + + # + # Make observers + # + sweep(; which_sweep, kw...) = which_sweep + sweep_observer! = observer(sweep) + + region(; which_region_update, sweep_plan, kw...) = first(sweep_plan[which_region_update]) + energy(; eigvals, kw...) = eigvals[1] + region_observer! = observer(region, sweep, energy) + + e, psi = dmrg(H, psi; nsweeps, maxdim, cutoff, sweep_observer!, region_observer!) + + # + # Test out certain values + # + @test region_observer![9, :region] == [2, 1] + @test region_observer![30, :energy] < -4.25 + @test region_observer![30, :energy] ≈ e rtol = 1.0e-6 end @testset "Cache to Disk" begin - N = 10 - cutoff = 1e-12 - s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end - H = mpo(os, s) - rng = StableRNG(1234) - psi = random_mps(rng, s; link_space=10) - - nsweeps = 4 - maxdim = [10, 20, 40, 80] - - @test_broken e, psi = dmrg( - H, - psi; - nsweeps, - maxdim, - cutoff, - outputlevel=0, - transform_operator=ITensorNetworks.cache_operator_to_disk, - transform_operator_kwargs=(; write_when_maxdim_exceeds=11), - ) + N = 10 + cutoff = 1.0e-12 + s = siteinds("S=1/2", N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = mpo(os, s) + rng = StableRNG(1234) + psi = random_mps(rng, s; link_space = 10) + + nsweeps = 4 + maxdim = [10, 20, 40, 80] + + @test_broken e, psi = dmrg( + H, + psi; + nsweeps, + maxdim, + cutoff, + outputlevel = 0, + transform_operator = ITensorNetworks.cache_operator_to_disk, + transform_operator_kwargs = (; write_when_maxdim_exceeds = 11), + ) end @testset "Regression test: Arrays of Parameters" begin - N = 10 - cutoff = 1e-12 + N = 10 + cutoff = 1.0e-12 - s = siteinds("S=1/2", N) + s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 - end + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end - H = mpo(os, s) + H = mpo(os, s) - rng = StableRNG(1234) - psi = random_mps(rng, s; link_space=20) + rng = StableRNG(1234) + psi = random_mps(rng, s; link_space = 20) - # Choose nsweeps to be less than length of arrays - nsweeps = 5 - maxdim = [200, 250, 400, 600, 800, 1200, 2000, 2400, 2600, 3000] - cutoff = [1e-10, 1e-10, 1e-12, 1e-12, 1e-12, 1e-12, 1e-14, 1e-14, 1e-14, 1e-14] + # Choose nsweeps to be less than length of arrays + nsweeps = 5 + maxdim = [200, 250, 400, 600, 800, 1200, 2000, 2400, 2600, 3000] + cutoff = [1.0e-10, 1.0e-10, 1.0e-12, 1.0e-12, 1.0e-12, 1.0e-12, 1.0e-14, 1.0e-14, 1.0e-14, 1.0e-14] - e, psi = dmrg(H, psi; nsweeps, maxdim, cutoff) + e, psi = dmrg(H, psi; nsweeps, maxdim, cutoff) end @testset "Tree DMRG" for nsites in [2] - cutoff = 1e-12 - - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) + cutoff = 1.0e-12 + + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + + @testset "SVD approach" for use_qns in [false, true] + auto_fermion_enabled = ITensors.using_auto_fermion() + if use_qns # test whether autofermion breaks things when using non-fermionic QNs + ITensors.enable_auto_fermion() + else # when using no QNs, autofermion breaks # ToDo reference Issue in ITensors + ITensors.disable_auto_fermion() + end + s = siteinds("S=1/2", c; conserve_qns = use_qns) + + os = ModelHamiltonians.heisenberg(c) + + H = ttn(os, s) + + # make init_state + d = Dict() + for (i, v) in enumerate(vertices(s)) + d[v] = isodd(i) ? "Up" : "Dn" + end + states = v -> d[v] + psi = ttn(states, s) + + # rng = StableRNG(1234) + # psi = random_ttn(rng, s; link_space=20) #FIXME: random_ttn broken for QN conserving case + + nsweeps = 10 + maxdim = [10, 20, 40, 100] + @show use_qns + e, psi = dmrg( + H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs = (; krylovdim = 3, maxiter = 1) + ) + + # Compare to `ITensors.MPO` version of `dmrg` + linear_order = [4, 1, 2, 5, 3, 6] + vmap = Dictionary(collect(vertices(s))[linear_order], 1:length(linear_order)) + sline = only.(collect(vertex_data(s)))[linear_order] + Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], os), sline) + rng = StableRNG(1234) + psiline = ITensorMPS.random_mps(rng, sline, i -> isodd(i) ? "Up" : "Dn"; linkdims = 20) + e2, psi2 = ITensorMPS.dmrg(Hline, psiline; nsweeps, maxdim, cutoff, outputlevel = 0) + + @test inner(psi', H, psi) ≈ ITensorMPS.inner(psi2', Hline, psi2) atol = 1.0e-5 + + if !auto_fermion_enabled + ITensors.disable_auto_fermion() + end + end +end - @testset "SVD approach" for use_qns in [false, true] +@testset "Tree DMRG for Fermions" for nsites in [2] #ToDo: change to [1,2] when random_ttn works with QNs auto_fermion_enabled = ITensors.using_auto_fermion() - if use_qns # test whether autofermion breaks things when using non-fermionic QNs - ITensors.enable_auto_fermion() - else # when using no QNs, autofermion breaks # ToDo reference Issue in ITensors - ITensors.disable_auto_fermion() - end - s = siteinds("S=1/2", c; conserve_qns=use_qns) + use_qns = true + cutoff = 1.0e-12 + nsweeps = 10 + maxdim = [10, 20, 40, 100] - os = ModelHamiltonians.heisenberg(c) + # setup model + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + s = siteinds("Electron", c; conserve_qns = use_qns) + U = 2.0 + t = 1.3 + tp = 0.6 + os = ModelHamiltonians.hubbard(c; U, t, tp) - H = ttn(os, s) + # for conversion to ITensors.MPO + linear_order = [4, 1, 2, 5, 3, 6] + vmap = Dictionary(collect(vertices(s))[linear_order], 1:length(linear_order)) + sline = only.(collect(vertex_data(s)))[linear_order] + + # get MPS / MPO with JW string result + ITensors.disable_auto_fermion() + Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], os), sline) + rng = StableRNG(1234) + psiline = ITensorMPS.random_mps(rng, sline, i -> isodd(i) ? "Up" : "Dn"; linkdims = 20) + e_jw, psi_jw = ITensorMPS.dmrg(Hline, psiline; nsweeps, maxdim, cutoff, outputlevel = 0) + ITensors.enable_auto_fermion() + # now get auto-fermion results + H = ttn(os, s) # make init_state d = Dict() for (i, v) in enumerate(vertices(s)) - d[v] = isodd(i) ? "Up" : "Dn" + d[v] = isodd(i) ? "Up" : "Dn" end states = v -> d[v] psi = ttn(states, s) - - # rng = StableRNG(1234) - # psi = random_ttn(rng, s; link_space=20) #FIXME: random_ttn broken for QN conserving case - - nsweeps = 10 - maxdim = [10, 20, 40, 100] - @show use_qns e, psi = dmrg( - H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs=(; krylovdim=3, maxiter=1) + H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs = (; krylovdim = 3, maxiter = 1) ) # Compare to `ITensors.MPO` version of `dmrg` - linear_order = [4, 1, 2, 5, 3, 6] - vmap = Dictionary(collect(vertices(s))[linear_order], 1:length(linear_order)) - sline = only.(collect(vertex_data(s)))[linear_order] Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], os), sline) rng = StableRNG(1234) - psiline = ITensorMPS.random_mps(rng, sline, i -> isodd(i) ? "Up" : "Dn"; linkdims=20) - e2, psi2 = ITensorMPS.dmrg(Hline, psiline; nsweeps, maxdim, cutoff, outputlevel=0) + psiline = ITensorMPS.random_mps(rng, sline, i -> isodd(i) ? "Up" : "Dn"; linkdims = 20) + e2, psi2 = ITensorMPS.dmrg(Hline, psiline; nsweeps, maxdim, cutoff, outputlevel = 0) - @test inner(psi', H, psi) ≈ ITensorMPS.inner(psi2', Hline, psi2) atol = 1e-5 + @test inner(psi', H, psi) ≈ ITensorMPS.inner(psi2', Hline, psi2) atol = 1.0e-5 + @test e2 ≈ e_jw atol = 1.0e-5 + @test inner(psi2', Hline, psi2) ≈ e_jw atol = 1.0e-5 if !auto_fermion_enabled - ITensors.disable_auto_fermion() + ITensors.disable_auto_fermion() end - end -end - -@testset "Tree DMRG for Fermions" for nsites in [2] #ToDo: change to [1,2] when random_ttn works with QNs - auto_fermion_enabled = ITensors.using_auto_fermion() - use_qns = true - cutoff = 1e-12 - nsweeps = 10 - maxdim = [10, 20, 40, 100] - - # setup model - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - s = siteinds("Electron", c; conserve_qns=use_qns) - U = 2.0 - t = 1.3 - tp = 0.6 - os = ModelHamiltonians.hubbard(c; U, t, tp) - - # for conversion to ITensors.MPO - linear_order = [4, 1, 2, 5, 3, 6] - vmap = Dictionary(collect(vertices(s))[linear_order], 1:length(linear_order)) - sline = only.(collect(vertex_data(s)))[linear_order] - - # get MPS / MPO with JW string result - ITensors.disable_auto_fermion() - Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], os), sline) - rng = StableRNG(1234) - psiline = ITensorMPS.random_mps(rng, sline, i -> isodd(i) ? "Up" : "Dn"; linkdims=20) - e_jw, psi_jw = ITensorMPS.dmrg(Hline, psiline; nsweeps, maxdim, cutoff, outputlevel=0) - ITensors.enable_auto_fermion() - - # now get auto-fermion results - H = ttn(os, s) - # make init_state - d = Dict() - for (i, v) in enumerate(vertices(s)) - d[v] = isodd(i) ? "Up" : "Dn" - end - states = v -> d[v] - psi = ttn(states, s) - e, psi = dmrg( - H, psi; nsweeps, maxdim, cutoff, nsites, updater_kwargs=(; krylovdim=3, maxiter=1) - ) - - # Compare to `ITensors.MPO` version of `dmrg` - Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], os), sline) - rng = StableRNG(1234) - psiline = ITensorMPS.random_mps(rng, sline, i -> isodd(i) ? "Up" : "Dn"; linkdims=20) - e2, psi2 = ITensorMPS.dmrg(Hline, psiline; nsweeps, maxdim, cutoff, outputlevel=0) - - @test inner(psi', H, psi) ≈ ITensorMPS.inner(psi2', Hline, psi2) atol = 1e-5 - @test e2 ≈ e_jw atol = 1e-5 - @test inner(psi2', Hline, psi2) ≈ e_jw atol = 1e-5 - - if !auto_fermion_enabled - ITensors.disable_auto_fermion() - end end @testset "Regression test: tree truncation" begin - maxdim = 4 - nsites = 2 - nsweeps = 10 - - rng = StableRNG(1234) - g = NamedGraph(uniform_tree(10)) - g = rename_vertices(v -> (v, 1), g) - s = siteinds("S=1/2", g) - os = ModelHamiltonians.heisenberg(g) - H = ttn(os, s) - psi = random_ttn(rng, s; link_space=5) - e, psi = dmrg(H, psi; nsweeps, maxdim, nsites) - - @test all(edge_data(linkdims(psi)) .<= maxdim) + maxdim = 4 + nsites = 2 + nsweeps = 10 + + rng = StableRNG(1234) + g = NamedGraph(uniform_tree(10)) + g = rename_vertices(v -> (v, 1), g) + s = siteinds("S=1/2", g) + os = ModelHamiltonians.heisenberg(g) + H = ttn(os, s) + psi = random_ttn(rng, s; link_space = 5) + e, psi = dmrg(H, psi; nsweeps, maxdim, nsites) + + @test all(edge_data(linkdims(psi)) .<= maxdim) end end diff --git a/test/test_ttn_dmrg_x.jl b/test/test_ttn_dmrg_x.jl index 4f2583ac..680b078c 100644 --- a/test/test_ttn_dmrg_x.jl +++ b/test/test_ttn_dmrg_x.jl @@ -2,7 +2,7 @@ using Dictionaries: Dictionary using Graphs: nv, vertices using ITensorNetworks: - OpSum, ttn, apply, contract, dmrg_x, inner, mpo, mps, random_mps, siteinds + OpSum, ttn, apply, contract, dmrg_x, inner, mpo, mps, random_mps, siteinds using ITensorNetworks.ModelHamiltonians: ModelHamiltonians using ITensors: @disable_warn_order, array, dag, onehot, uniqueind using LinearAlgebra: eigen, normalize @@ -11,60 +11,60 @@ using StableRNGs: StableRNG using Test: @test, @testset # TODO: Combine MPS and TTN tests. @testset "MPS DMRG-X" for conserve_qns in (false, true) - n = 10 - s = siteinds("S=1/2", n; conserve_qns) - W = 12 - # Random fields h ∈ [-W, W] - rng = StableRNG(1234) - h = W * (2 * rand(rng, n) .- 1) - H = mpo(ModelHamiltonians.heisenberg(n; h), s) - ψ = mps(v -> rand(rng, ["↑", "↓"]), s) - dmrg_x_kwargs = (nsweeps=20, normalize=true, maxdim=20, cutoff=1e-10, outputlevel=0) - e, ϕ = dmrg_x(H, ψ; nsites=2, dmrg_x_kwargs...) - @test inner(ϕ', H, ϕ) / inner(ϕ, ϕ) ≈ e - @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ', H, ϕ) / inner(ϕ, ϕ) rtol = 1e-1 - @test inner(H, ψ, H, ψ) ≉ inner(ψ', H, ψ)^2 rtol = 1e-7 - @test inner(H, ϕ, H, ϕ) ≈ inner(ϕ', H, ϕ)^2 rtol = 1e-7 - e, ϕ̃ = dmrg_x(H, ϕ; nsites=1, dmrg_x_kwargs...) - @test inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) ≈ e - @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) rtol = 1e-1 - @test inner(H, ϕ̃, H, ϕ̃) ≈ inner(ϕ̃', H, ϕ̃)^2 rtol = 1e-3 - # Sometimes broken, sometimes not - # @test abs(loginner(ϕ̃, ϕ) / n) ≈ 0.0 atol = 1e-6 + n = 10 + s = siteinds("S=1/2", n; conserve_qns) + W = 12 + # Random fields h ∈ [-W, W] + rng = StableRNG(1234) + h = W * (2 * rand(rng, n) .- 1) + H = mpo(ModelHamiltonians.heisenberg(n; h), s) + ψ = mps(v -> rand(rng, ["↑", "↓"]), s) + dmrg_x_kwargs = (nsweeps = 20, normalize = true, maxdim = 20, cutoff = 1.0e-10, outputlevel = 0) + e, ϕ = dmrg_x(H, ψ; nsites = 2, dmrg_x_kwargs...) + @test inner(ϕ', H, ϕ) / inner(ϕ, ϕ) ≈ e + @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ', H, ϕ) / inner(ϕ, ϕ) rtol = 1.0e-1 + @test inner(H, ψ, H, ψ) ≉ inner(ψ', H, ψ)^2 rtol = 1.0e-7 + @test inner(H, ϕ, H, ϕ) ≈ inner(ϕ', H, ϕ)^2 rtol = 1.0e-7 + e, ϕ̃ = dmrg_x(H, ϕ; nsites = 1, dmrg_x_kwargs...) + @test inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) ≈ e + @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) rtol = 1.0e-1 + @test inner(H, ϕ̃, H, ϕ̃) ≈ inner(ϕ̃', H, ϕ̃)^2 rtol = 1.0e-3 + # Sometimes broken, sometimes not + # @test abs(loginner(ϕ̃, ϕ) / n) ≈ 0.0 atol = 1e-6 end @testset "Tree DMRG-X" for conserve_qns in (false, true) - # TODO: Combine with tests above into a loop over graph structures. - tooth_lengths = fill(2, 3) - root_vertex = (3, 2) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c; conserve_qns) - W = 12 - # Random fields h ∈ [-W, W] - rng = StableRNG(123) - h = Dictionary(vertices(c), W * (2 * rand(rng, nv(c)) .- 1)) - H = ttn(ModelHamiltonians.heisenberg(c; h), s) - ψ = normalize(ttn(v -> rand(rng, ["↑", "↓"]), s)) - dmrg_x_kwargs = (nsweeps=20, normalize=true, maxdim=20, cutoff=1e-10, outputlevel=0) - e, ϕ = dmrg_x(H, ψ; nsites=2, dmrg_x_kwargs...) - @test inner(ϕ', H, ϕ) / inner(ϕ, ϕ) ≈ e - @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ', H, ϕ) / inner(ϕ, ϕ) rtol = 1e-1 - @test inner(H, ψ, H, ψ) ≉ inner(ψ', H, ψ)^2 rtol = 1e-2 - @test inner(H, ϕ, H, ϕ) ≈ inner(ϕ', H, ϕ)^2 rtol = 1e-7 - e, ϕ̃ = dmrg_x(H, ϕ; nsites=1, dmrg_x_kwargs...) - @test inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) ≈ e - @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) rtol = 1e-1 - @test inner(H, ϕ̃, H, ϕ̃) ≈ inner(ϕ̃', H, ϕ̃)^2 rtol = 1e-6 - # Sometimes broken, sometimes not - # @test abs(loginner(ϕ̃, ϕ) / nv(c)) ≈ 0.0 atol = 1e-8 - # compare against ED - @disable_warn_order U0 = contract(ψ, root_vertex) - @disable_warn_order T = contract(H, root_vertex) - D, U = eigen(T; ishermitian=true) - u = uniqueind(U, T) - _, max_ind = findmax(abs, array(dag(U0) * U)) - U_exact = U * dag(onehot(u => max_ind)) - @disable_warn_order U_dmrgx = contract(ϕ, root_vertex) - @test inner(ϕ', H, ϕ) ≈ (dag(U_exact') * T * U_exact)[] atol = 1e-6 - @test abs(inner(U_dmrgx, U_exact)) ≈ 1 atol = 1e-6 + # TODO: Combine with tests above into a loop over graph structures. + tooth_lengths = fill(2, 3) + root_vertex = (3, 2) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c; conserve_qns) + W = 12 + # Random fields h ∈ [-W, W] + rng = StableRNG(123) + h = Dictionary(vertices(c), W * (2 * rand(rng, nv(c)) .- 1)) + H = ttn(ModelHamiltonians.heisenberg(c; h), s) + ψ = normalize(ttn(v -> rand(rng, ["↑", "↓"]), s)) + dmrg_x_kwargs = (nsweeps = 20, normalize = true, maxdim = 20, cutoff = 1.0e-10, outputlevel = 0) + e, ϕ = dmrg_x(H, ψ; nsites = 2, dmrg_x_kwargs...) + @test inner(ϕ', H, ϕ) / inner(ϕ, ϕ) ≈ e + @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ', H, ϕ) / inner(ϕ, ϕ) rtol = 1.0e-1 + @test inner(H, ψ, H, ψ) ≉ inner(ψ', H, ψ)^2 rtol = 1.0e-2 + @test inner(H, ϕ, H, ϕ) ≈ inner(ϕ', H, ϕ)^2 rtol = 1.0e-7 + e, ϕ̃ = dmrg_x(H, ϕ; nsites = 1, dmrg_x_kwargs...) + @test inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) ≈ e + @test inner(ψ', H, ψ) / inner(ψ, ψ) ≈ inner(ϕ̃', H, ϕ̃) / inner(ϕ̃, ϕ̃) rtol = 1.0e-1 + @test inner(H, ϕ̃, H, ϕ̃) ≈ inner(ϕ̃', H, ϕ̃)^2 rtol = 1.0e-6 + # Sometimes broken, sometimes not + # @test abs(loginner(ϕ̃, ϕ) / nv(c)) ≈ 0.0 atol = 1e-8 + # compare against ED + @disable_warn_order U0 = contract(ψ, root_vertex) + @disable_warn_order T = contract(H, root_vertex) + D, U = eigen(T; ishermitian = true) + u = uniqueind(U, T) + _, max_ind = findmax(abs, array(dag(U0) * U)) + U_exact = U * dag(onehot(u => max_ind)) + @disable_warn_order U_dmrgx = contract(ϕ, root_vertex) + @test inner(ϕ', H, ϕ) ≈ (dag(U_exact') * T * U_exact)[] atol = 1.0e-6 + @test abs(inner(U_dmrgx, U_exact)) ≈ 1 atol = 1.0e-6 end end diff --git a/test/test_ttn_expect.jl b/test/test_ttn_expect.jl index f819f109..bb7723c4 100644 --- a/test/test_ttn_expect.jl +++ b/test/test_ttn_expect.jl @@ -6,18 +6,18 @@ using NamedGraphs.NamedGraphGenerators: named_comb_tree using StableRNGs: StableRNG using Test: @test, @testset @testset "TTN expect" begin - tooth_lengths = fill(2, 2) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - d = Dict() - magnetization = Dict() - for (i, v) in enumerate(vertices(s)) - d[v] = isodd(i) ? "Up" : "Dn" - magnetization[v] = isodd(i) ? 0.5 : -0.5 - end - states = v -> d[v] - state = ttn(states, s) - res = expect("Sz", state) - @test all([isapprox(res[v], magnetization[v]; atol=1e-8) for v in vertices(s)]) + tooth_lengths = fill(2, 2) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) + d = Dict() + magnetization = Dict() + for (i, v) in enumerate(vertices(s)) + d[v] = isodd(i) ? "Up" : "Dn" + magnetization[v] = isodd(i) ? 0.5 : -0.5 + end + states = v -> d[v] + state = ttn(states, s) + res = expect("Sz", state) + @test all([isapprox(res[v], magnetization[v]; atol = 1.0e-8) for v in vertices(s)]) end end diff --git a/test/test_ttn_linsolve.jl b/test/test_ttn_linsolve.jl index dab969ed..324d11e9 100644 --- a/test/test_ttn_linsolve.jl +++ b/test/test_ttn_linsolve.jl @@ -5,44 +5,44 @@ using StableRNGs: StableRNG using Test: @test, @test_broken, @testset @testset "Linsolve" begin - @testset "Linsolve Basics" begin - cutoff = 1E-11 - maxdim = 8 - nsweeps = 2 - - N = 8 - # s = siteinds("S=1/2", N; conserve_qns=true) - s = siteinds("S=1/2", N; conserve_qns=false) - - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + @testset "Linsolve Basics" begin + cutoff = 1.0e-11 + maxdim = 8 + nsweeps = 2 + + N = 8 + # s = siteinds("S=1/2", N; conserve_qns=true) + s = siteinds("S=1/2", N; conserve_qns = false) + + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = mpo(os, s) + + # + # Test complex case + # + + rng = StableRNG(1234) + ## TODO: Need to add support for `random_mps`/`random_tensornetwork` with state input. + ## states = [isodd(n) ? "Up" : "Dn" for n in 1:N] + ## x_c = random_mps(rng, states, s; link_space=4) + 0.1im * random_mps(rng, states, s; link_space=2) + x_c = random_mps(rng, s; link_space = 4) + 0.1im * random_mps(rng, s; link_space = 2) + + b = apply(H, x_c; alg = "fit", nsweeps = 3, init = x_c) #cutoff is unsupported kwarg for apply/contract + + ## TODO: Need to add support for `random_mps`/`random_tensornetwork` with state input. + ## x0 = random_mps(rng, states, s; link_space=10) + x0 = random_mps(rng, s; link_space = 10) + + x = @test_broken linsolve( + H, b, x0; cutoff, maxdim, nsweeps, updater_kwargs = (; tol = 1.0e-6, ishermitian = true) + ) + + # @test norm(x - x_c) < 1E-3 end - H = mpo(os, s) - - # - # Test complex case - # - - rng = StableRNG(1234) - ## TODO: Need to add support for `random_mps`/`random_tensornetwork` with state input. - ## states = [isodd(n) ? "Up" : "Dn" for n in 1:N] - ## x_c = random_mps(rng, states, s; link_space=4) + 0.1im * random_mps(rng, states, s; link_space=2) - x_c = random_mps(rng, s; link_space=4) + 0.1im * random_mps(rng, s; link_space=2) - - b = apply(H, x_c; alg="fit", nsweeps=3, init=x_c) #cutoff is unsupported kwarg for apply/contract - - ## TODO: Need to add support for `random_mps`/`random_tensornetwork` with state input. - ## x0 = random_mps(rng, states, s; link_space=10) - x0 = random_mps(rng, s; link_space=10) - - x = @test_broken linsolve( - H, b, x0; cutoff, maxdim, nsweeps, updater_kwargs=(; tol=1E-6, ishermitian=true) - ) - - # @test norm(x - x_c) < 1E-3 - end end end diff --git a/test/test_ttn_position.jl b/test/test_ttn_position.jl index 86916881..5c2309fb 100644 --- a/test/test_ttn_position.jl +++ b/test/test_ttn_position.jl @@ -9,51 +9,51 @@ using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_path_graph using Test: @test, @testset @testset "ProjTTN position" begin - # make a nontrivial TTN state and TTN operator - - auto_fermion_enabled = ITensors.using_auto_fermion() - use_qns = true - cutoff = 1e-12 - - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - if use_qns # test whether autofermion breaks things when using non-fermionic QNs - ITensors.enable_auto_fermion() - else # when using no QNs, autofermion breaks # ToDo reference Issue in ITensors - ITensors.disable_auto_fermion() - end - s = siteinds("S=1/2", c; conserve_qns=use_qns) - - os = ModelHamiltonians.heisenberg(c) - - H = ttn(os, s) - - d = Dict() - for (i, v) in enumerate(vertices(s)) - d[v] = isodd(i) ? "Up" : "Dn" - end - states = v -> d[v] - psi = ttn(states, s) - - # actual test, verifies that position is out of place - vs = collect(vertices(s)) - PH = ProjTTN(H) - PH = position(PH, psi, [vs[2]]) - original_keys = deepcopy(keys(environments(PH))) - # test out-of-placeness of position - PHc = position(PH, psi, [vs[2], vs[5]]) - @test keys(environments(PH)) == original_keys - @test keys(environments(PHc)) != original_keys - - if !auto_fermion_enabled - ITensors.disable_auto_fermion() - end + # make a nontrivial TTN state and TTN operator + + auto_fermion_enabled = ITensors.using_auto_fermion() + use_qns = true + cutoff = 1.0e-12 + + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + if use_qns # test whether autofermion breaks things when using non-fermionic QNs + ITensors.enable_auto_fermion() + else # when using no QNs, autofermion breaks # ToDo reference Issue in ITensors + ITensors.disable_auto_fermion() + end + s = siteinds("S=1/2", c; conserve_qns = use_qns) + + os = ModelHamiltonians.heisenberg(c) + + H = ttn(os, s) + + d = Dict() + for (i, v) in enumerate(vertices(s)) + d[v] = isodd(i) ? "Up" : "Dn" + end + states = v -> d[v] + psi = ttn(states, s) + + # actual test, verifies that position is out of place + vs = collect(vertices(s)) + PH = ProjTTN(H) + PH = position(PH, psi, [vs[2]]) + original_keys = deepcopy(keys(environments(PH))) + # test out-of-placeness of position + PHc = position(PH, psi, [vs[2], vs[5]]) + @test keys(environments(PH)) == original_keys + @test keys(environments(PHc)) != original_keys + + if !auto_fermion_enabled + ITensors.disable_auto_fermion() + end end @testset "ProjTTN construction regression test" begin - pos = Indices{Tuple{String,Int}}() - g = named_path_graph(2) - operator = ttn(ITensorNetwork{Any}(g)) - environments = Dictionary{NamedEdge{Any},ITensor}() - @test ProjTTN(pos, operator, environments) isa ProjTTN{Any,Indices{Any}} + pos = Indices{Tuple{String, Int}}() + g = named_path_graph(2) + operator = ttn(ITensorNetwork{Any}(g)) + environments = Dictionary{NamedEdge{Any}, ITensor}() + @test ProjTTN(pos, operator, environments) isa ProjTTN{Any, Indices{Any}} end end diff --git a/test/test_ttn_tdvp.jl b/test/test_ttn_tdvp.jl index f4426d21..b666251c 100644 --- a/test/test_ttn_tdvp.jl +++ b/test/test_ttn_tdvp.jl @@ -2,18 +2,18 @@ using Graphs: dst, edges, src using ITensors: ITensor, contract, dag, inner, noprime, normalize, prime, scalar using ITensorNetworks: - ITensorNetworks, - OpSum, - ttn, - apply, - expect, - mpo, - mps, - op, - random_mps, - random_ttn, - siteinds, - tdvp + ITensorNetworks, + OpSum, + ttn, + apply, + expect, + mpo, + mps, + op, + random_mps, + random_ttn, + siteinds, + tdvp using ITensorNetworks.ModelHamiltonians: ModelHamiltonians using LinearAlgebra: norm using NamedGraphs.NamedGraphGenerators: named_binary_tree, named_comb_tree @@ -21,643 +21,643 @@ using Observers: observer using StableRNGs: StableRNG using Test: @testset, @test @testset "MPS TDVP" begin - @testset "Basic TDVP" begin - N = 10 - cutoff = 1e-12 - - s = siteinds("S=1/2", N) - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + @testset "Basic TDVP" begin + N = 10 + cutoff = 1.0e-12 + + s = siteinds("S=1/2", N) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + + H = mpo(os, s) + + rng = StableRNG(1234) + ψ0 = random_mps(rng, s; link_space = 10) + + # Time evolve forward: + ψ1 = tdvp(H, -0.1im, ψ0; nsweeps = 1, cutoff, nsites = 1) + @test norm(ψ1) ≈ 1.0 + + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 + + # Average energy should be conserved: + @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) + + # Time evolve backwards: + ψ2 = tdvp( + H, + +0.1im, + ψ1; + nsweeps = 1, + cutoff, + updater_kwargs = (; krylovdim = 20, maxiter = 20, tol = 1.0e-8), + ) + + @test norm(ψ2) ≈ 1.0 + + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 + + # test different ways to specify time-step specifications + ψa = tdvp(H, -0.1im, ψ0; nsweeps = 4, cutoff, nsites = 1) + ψb = tdvp(H, -0.1im, ψ0; time_step = -0.025im, cutoff, nsites = 1) + ψc = tdvp( + H, -0.1im, ψ0; time_step = [-0.02im, -0.03im, -0.015im, -0.035im], cutoff, nsites = 1 + ) + ψd = tdvp( + H, -0.1im, ψ0; nsweeps = 4, time_step = [-0.02im, -0.03im, -0.025im], cutoff, nsites = 1 + ) + @test inner(ψa, ψb) ≈ 1.0 rtol = 1.0e-7 + @test inner(ψa, ψc) ≈ 1.0 rtol = 1.0e-7 + @test inner(ψa, ψd) ≈ 1.0 rtol = 1.0e-7 end - H = mpo(os, s) - - rng = StableRNG(1234) - ψ0 = random_mps(rng, s; link_space=10) - - # Time evolve forward: - ψ1 = tdvp(H, -0.1im, ψ0; nsweeps=1, cutoff, nsites=1) - @test norm(ψ1) ≈ 1.0 - - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 - - # Average energy should be conserved: - @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) - - # Time evolve backwards: - ψ2 = tdvp( - H, - +0.1im, - ψ1; - nsweeps=1, - cutoff, - updater_kwargs=(; krylovdim=20, maxiter=20, tol=1e-8), - ) - - @test norm(ψ2) ≈ 1.0 - - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 - - # test different ways to specify time-step specifications - ψa = tdvp(H, -0.1im, ψ0; nsweeps=4, cutoff, nsites=1) - ψb = tdvp(H, -0.1im, ψ0; time_step=-0.025im, cutoff, nsites=1) - ψc = tdvp( - H, -0.1im, ψ0; time_step=[-0.02im, -0.03im, -0.015im, -0.035im], cutoff, nsites=1 - ) - ψd = tdvp( - H, -0.1im, ψ0; nsweeps=4, time_step=[-0.02im, -0.03im, -0.025im], cutoff, nsites=1 - ) - @test inner(ψa, ψb) ≈ 1.0 rtol = 1e-7 - @test inner(ψa, ψc) ≈ 1.0 rtol = 1e-7 - @test inner(ψa, ψd) ≈ 1.0 rtol = 1e-7 - end - - @testset "TDVP: Sum of Hamiltonians" begin - N = 10 - cutoff = 1e-10 - - s = siteinds("S=1/2", N) - - os1 = OpSum() - for j in 1:(N - 1) - os1 += 0.5, "S+", j, "S-", j + 1 - os1 += 0.5, "S-", j, "S+", j + 1 - end - os2 = OpSum() - for j in 1:(N - 1) - os2 += "Sz", j, "Sz", j + 1 - end - - H1 = mpo(os1, s) - H2 = mpo(os2, s) - Hs = [H1, H2] + @testset "TDVP: Sum of Hamiltonians" begin + N = 10 + cutoff = 1.0e-10 - rng = StableRNG(1234) - ψ0 = random_mps(rng, s; link_space=10) + s = siteinds("S=1/2", N) - ψ1 = tdvp(Hs, -0.1im, ψ0; nsweeps=1, cutoff, nsites=1) + os1 = OpSum() + for j in 1:(N - 1) + os1 += 0.5, "S+", j, "S-", j + 1 + os1 += 0.5, "S-", j, "S+", j + 1 + end + os2 = OpSum() + for j in 1:(N - 1) + os2 += "Sz", j, "Sz", j + 1 + end - @test norm(ψ1) ≈ 1.0 + H1 = mpo(os1, s) + H2 = mpo(os2, s) + Hs = [H1, H2] - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 + rng = StableRNG(1234) + ψ0 = random_mps(rng, s; link_space = 10) - # Average energy should be conserved: - @test real(sum(H -> inner(ψ1', H, ψ1), Hs)) ≈ sum(H -> inner(ψ0', H, ψ0), Hs) + ψ1 = tdvp(Hs, -0.1im, ψ0; nsweeps = 1, cutoff, nsites = 1) - # Time evolve backwards: - ψ2 = tdvp(Hs, +0.1im, ψ1; nsweeps=1, cutoff) + @test norm(ψ1) ≈ 1.0 - @test norm(ψ2) ≈ 1.0 + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 - end + # Average energy should be conserved: + @test real(sum(H -> inner(ψ1', H, ψ1), Hs)) ≈ sum(H -> inner(ψ0', H, ψ0), Hs) - @testset "Higher-Order TDVP" begin - N = 10 - cutoff = 1e-12 - order = 4 + # Time evolve backwards: + ψ2 = tdvp(Hs, +0.1im, ψ1; nsweeps = 1, cutoff) - s = siteinds("S=1/2", N) + @test norm(ψ2) ≈ 1.0 - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 end - H = mpo(os, s) + @testset "Higher-Order TDVP" begin + N = 10 + cutoff = 1.0e-12 + order = 4 - rng = StableRNG(1234) - ψ0 = random_mps(rng, s; link_space=10) + s = siteinds("S=1/2", N) - # Time evolve forward: - ψ1 = tdvp(H, -0.1im, ψ0; time_step=-0.05im, order, cutoff, nsites=1) + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end - @test norm(ψ1) ≈ 1.0 + H = mpo(os, s) - # Average energy should be conserved: - @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) + rng = StableRNG(1234) + ψ0 = random_mps(rng, s; link_space = 10) - # Time evolve backwards: - ψ2 = tdvp(H, +0.1im, ψ1; time_step=+0.05im, order, cutoff) + # Time evolve forward: + ψ1 = tdvp(H, -0.1im, ψ0; time_step = -0.05im, order, cutoff, nsites = 1) - @test norm(ψ2) ≈ 1.0 + @test norm(ψ1) ≈ 1.0 - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 - end + # Average energy should be conserved: + @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) - @testset "Accuracy Test" begin - N = 4 - tau = 0.1 - ttotal = 1.0 - cutoff = 1e-12 + # Time evolve backwards: + ψ2 = tdvp(H, +0.1im, ψ1; time_step = +0.05im, order, cutoff) - s = siteinds("S=1/2", N; conserve_qns=false) + @test norm(ψ2) ≈ 1.0 - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 end - H = mpo(os, s) - HM = contract(H) - - Ut = exp(-im * tau * HM) - - state = mps(n -> isodd(n) ? "Up" : "Dn", s) - psi2 = deepcopy(state) - psix = contract(state) - - Sz_tdvp = Float64[] - Sz_tdvp2 = Float64[] - Sz_exact = Float64[] - - c = div(N, 2) - Szc = op("Sz", s[c]) - - Nsteps = Int(ttotal / tau) - for step in 1:Nsteps - psix = noprime(Ut * psix) - psix /= norm(psix) - - state = tdvp( - H, - -im * tau, - state; - cutoff, - normalize=false, - updater_kwargs=(; tol=1e-12, maxiter=500, krylovdim=25), - ) - # TODO: What should `expect` output? Right now - # it outputs a dictionary. - push!(Sz_tdvp, real(expect("Sz", state; vertices=[c])[c])) - - psi2 = tdvp( - H, - -im * tau, - psi2; - cutoff, - normalize=false, - updater_kwargs=(; tol=1e-12, maxiter=500, krylovdim=25), - updater=ITensorNetworks.exponentiate_updater, - ) - # TODO: What should `expect` output? Right now - # it outputs a dictionary. - push!(Sz_tdvp2, real(expect("Sz", psi2; vertices=[c])[c])) - - push!(Sz_exact, real(scalar(dag(prime(psix, s[c])) * Szc * psix))) - F = abs(scalar(dag(psix) * contract(state))) - end - - @test norm(Sz_tdvp - Sz_exact) < 1e-5 - @test norm(Sz_tdvp2 - Sz_exact) < 1e-5 - end - @testset "TEBD Comparison" begin - N = 10 - cutoff = 1e-12 - tau = 0.1 - ttotal = 1.0 - - s = siteinds("S=1/2", N; conserve_qns=true) - - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + @testset "Accuracy Test" begin + N = 4 + tau = 0.1 + ttotal = 1.0 + cutoff = 1.0e-12 + + s = siteinds("S=1/2", N; conserve_qns = false) + + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = mpo(os, s) + HM = contract(H) + + Ut = exp(-im * tau * HM) + + state = mps(n -> isodd(n) ? "Up" : "Dn", s) + psi2 = deepcopy(state) + psix = contract(state) + + Sz_tdvp = Float64[] + Sz_tdvp2 = Float64[] + Sz_exact = Float64[] + + c = div(N, 2) + Szc = op("Sz", s[c]) + + Nsteps = Int(ttotal / tau) + for step in 1:Nsteps + psix = noprime(Ut * psix) + psix /= norm(psix) + + state = tdvp( + H, + -im * tau, + state; + cutoff, + normalize = false, + updater_kwargs = (; tol = 1.0e-12, maxiter = 500, krylovdim = 25), + ) + # TODO: What should `expect` output? Right now + # it outputs a dictionary. + push!(Sz_tdvp, real(expect("Sz", state; vertices = [c])[c])) + + psi2 = tdvp( + H, + -im * tau, + psi2; + cutoff, + normalize = false, + updater_kwargs = (; tol = 1.0e-12, maxiter = 500, krylovdim = 25), + updater = ITensorNetworks.exponentiate_updater, + ) + # TODO: What should `expect` output? Right now + # it outputs a dictionary. + push!(Sz_tdvp2, real(expect("Sz", psi2; vertices = [c])[c])) + + push!(Sz_exact, real(scalar(dag(prime(psix, s[c])) * Szc * psix))) + F = abs(scalar(dag(psix) * contract(state))) + end + + @test norm(Sz_tdvp - Sz_exact) < 1.0e-5 + @test norm(Sz_tdvp2 - Sz_exact) < 1.0e-5 end - H = mpo(os, s) - - gates = ITensor[] - for j in 1:(N - 1) - s1 = s[j] - s2 = s[j + 1] - hj = - op("Sz", s1) * op("Sz", s2) + - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) - Gj = exp(-1.0im * tau / 2 * hj) - push!(gates, Gj) - end - append!(gates, reverse(gates)) - - state = mps(n -> isodd(n) ? "Up" : "Dn", s) - phi = deepcopy(state) - c = div(N, 2) - - # - # Evolve using TEBD - # - - Nsteps = convert(Int, ceil(abs(ttotal / tau))) - Sz1 = zeros(Nsteps) - En1 = zeros(Nsteps) - #Sz2 = zeros(Nsteps) - #En2 = zeros(Nsteps) - - for step in 1:Nsteps - state = apply(gates, state; cutoff) - - nsites = (step <= 3 ? 2 : 1) - phi = tdvp( - H, - -tau * im, - phi; - nsweeps=1, - cutoff, - nsites, - normalize=true, - updater_kwargs=(; krylovdim=15), - ) - - Sz1[step] = real(expect("Sz", state; vertices=[c])[c]) - #Sz2[step] = real(expect("Sz", phi; vertices=[c])[c]) - En1[step] = real(inner(state', H, state)) - #En2[step] = real(inner(phi', H, phi)) + @testset "TEBD Comparison" begin + N = 10 + cutoff = 1.0e-12 + tau = 0.1 + ttotal = 1.0 + + s = siteinds("S=1/2", N; conserve_qns = true) + + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + + H = mpo(os, s) + + gates = ITensor[] + for j in 1:(N - 1) + s1 = s[j] + s2 = s[j + 1] + hj = + op("Sz", s1) * op("Sz", s2) + + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + Gj = exp(-1.0im * tau / 2 * hj) + push!(gates, Gj) + end + append!(gates, reverse(gates)) + + state = mps(n -> isodd(n) ? "Up" : "Dn", s) + phi = deepcopy(state) + c = div(N, 2) + + # + # Evolve using TEBD + # + + Nsteps = convert(Int, ceil(abs(ttotal / tau))) + Sz1 = zeros(Nsteps) + En1 = zeros(Nsteps) + #Sz2 = zeros(Nsteps) + #En2 = zeros(Nsteps) + + for step in 1:Nsteps + state = apply(gates, state; cutoff) + + nsites = (step <= 3 ? 2 : 1) + phi = tdvp( + H, + -tau * im, + phi; + nsweeps = 1, + cutoff, + nsites, + normalize = true, + updater_kwargs = (; krylovdim = 15), + ) + + Sz1[step] = real(expect("Sz", state; vertices = [c])[c]) + #Sz2[step] = real(expect("Sz", phi; vertices=[c])[c]) + En1[step] = real(inner(state', H, state)) + #En2[step] = real(inner(phi', H, phi)) + end + + # + # Evolve using TDVP + # + + phi = mps(n -> isodd(n) ? "Up" : "Dn", s) + + obs = observer( + "Sz" => (; state) -> expect("Sz", state; vertices = [c])[c], + "En" => (; state) -> real(inner(state', H, state)), + ) + + phi = tdvp( + H, + -im * ttotal, + phi; + time_step = -im * tau, + cutoff, + normalize = false, + (sweep_observer!) = obs, + root_vertex = N, # defaults to 1, which breaks observer equality + ) + + Sz2 = obs.Sz + En2 = obs.En + @test norm(Sz1 - Sz2) < 1.0e-3 + @test norm(En1 - En2) < 1.0e-3 end - # - # Evolve using TDVP - # - - phi = mps(n -> isodd(n) ? "Up" : "Dn", s) - - obs = observer( - "Sz" => (; state) -> expect("Sz", state; vertices=[c])[c], - "En" => (; state) -> real(inner(state', H, state)), - ) - - phi = tdvp( - H, - -im * ttotal, - phi; - time_step=-im * tau, - cutoff, - normalize=false, - (sweep_observer!)=obs, - root_vertex=N, # defaults to 1, which breaks observer equality - ) - - Sz2 = obs.Sz - En2 = obs.En - @test norm(Sz1 - Sz2) < 1e-3 - @test norm(En1 - En2) < 1e-3 - end - - @testset "Imaginary Time Evolution" for reverse_step in [true, false] - cutoff = 1e-12 - tau = 1.0 - ttotal = 10.0 - N = 10 - s = siteinds("S=1/2", N) - - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + @testset "Imaginary Time Evolution" for reverse_step in [true, false] + cutoff = 1.0e-12 + tau = 1.0 + ttotal = 10.0 + N = 10 + s = siteinds("S=1/2", N) + + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + + H = mpo(os, s) + + rng = StableRNG(1234) + state = random_mps(rng, s; link_space = 2) + en0 = inner(state', H, state) + nsites = [repeat([2], 10); repeat([1], 10)] + maxdim = 32 + state = tdvp( + H, + -ttotal, + state; + time_step = (-tau), + maxdim, + cutoff, + nsites, + reverse_step, + normalize = true, + updater_kwargs = (; krylovdim = 15), + ) + en1 = inner(state', H, state) + @test en1 < en0 end - H = mpo(os, s) - - rng = StableRNG(1234) - state = random_mps(rng, s; link_space=2) - en0 = inner(state', H, state) - nsites = [repeat([2], 10); repeat([1], 10)] - maxdim = 32 - state = tdvp( - H, - -ttotal, - state; - time_step=(-tau), - maxdim, - cutoff, - nsites, - reverse_step, - normalize=true, - updater_kwargs=(; krylovdim=15), - ) - en1 = inner(state', H, state) - @test en1 < en0 - end - - @testset "Observers" begin - N = 10 - cutoff = 1e-12 - tau = 0.1 - ttotal = 1.0 - - s = siteinds("S=1/2", N; conserve_qns=true) - - os = OpSum() - for j in 1:(N - 1) - os += 0.5, "S+", j, "S-", j + 1 - os += 0.5, "S-", j, "S+", j + 1 - os += "Sz", j, "Sz", j + 1 + @testset "Observers" begin + N = 10 + cutoff = 1.0e-12 + tau = 0.1 + ttotal = 1.0 + + s = siteinds("S=1/2", N; conserve_qns = true) + + os = OpSum() + for j in 1:(N - 1) + os += 0.5, "S+", j, "S-", j + 1 + os += 0.5, "S-", j, "S+", j + 1 + os += "Sz", j, "Sz", j + 1 + end + H = mpo(os, s) + + c = div(N, 2) + + # + # Using Observers.jl + # + + measure_sz(; state) = expect("Sz", state; vertices = [c])[c] + measure_en(; state) = real(inner(state', H, state)) + sweep_obs = observer("Sz" => measure_sz, "En" => measure_en) + + get_info(; info) = info + step_measure_sz(; state) = expect("Sz", state; vertices = [c])[c] + step_measure_en(; state) = real(inner(state', H, state)) + region_obs = observer( + "Sz" => step_measure_sz, "En" => step_measure_en, "info" => get_info + ) + + state2 = mps(n -> isodd(n) ? "Up" : "Dn", s) + tdvp( + H, + -im * ttotal, + state2; + time_step = -im * tau, + cutoff, + normalize = false, + (sweep_observer!) = sweep_obs, + (region_observer!) = region_obs, + root_vertex = N, # defaults to 1, which breaks observer equality + ) + + Sz2 = sweep_obs.Sz + En2 = sweep_obs.En + + Sz2_step = region_obs.Sz + En2_step = region_obs.En + infos = region_obs.info + + # + # Could use ideas of other things to test here + # + + @test all(x -> x.converged == 1, infos) end - H = mpo(os, s) - - c = div(N, 2) - - # - # Using Observers.jl - # - - measure_sz(; state) = expect("Sz", state; vertices=[c])[c] - measure_en(; state) = real(inner(state', H, state)) - sweep_obs = observer("Sz" => measure_sz, "En" => measure_en) - - get_info(; info) = info - step_measure_sz(; state) = expect("Sz", state; vertices=[c])[c] - step_measure_en(; state) = real(inner(state', H, state)) - region_obs = observer( - "Sz" => step_measure_sz, "En" => step_measure_en, "info" => get_info - ) - - state2 = mps(n -> isodd(n) ? "Up" : "Dn", s) - tdvp( - H, - -im * ttotal, - state2; - time_step=-im * tau, - cutoff, - normalize=false, - (sweep_observer!)=sweep_obs, - (region_observer!)=region_obs, - root_vertex=N, # defaults to 1, which breaks observer equality - ) - - Sz2 = sweep_obs.Sz - En2 = sweep_obs.En - - Sz2_step = region_obs.Sz - En2_step = region_obs.En - infos = region_obs.info - - # - # Could use ideas of other things to test here - # - - @test all(x -> x.converged == 1, infos) - end end @testset "Tree TDVP" begin - @testset "Basic TDVP" for c in [named_comb_tree(fill(2, 3)), named_binary_tree(3)] - cutoff = 1e-12 - - tooth_lengths = fill(4, 4) - root_vertex = (1, 4) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - - os = ModelHamiltonians.heisenberg(c) + @testset "Basic TDVP" for c in [named_comb_tree(fill(2, 3)), named_binary_tree(3)] + cutoff = 1.0e-12 - H = ttn(os, s) + tooth_lengths = fill(4, 4) + root_vertex = (1, 4) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) - rng = StableRNG(1234) - ψ0 = normalize(random_ttn(rng, s)) + os = ModelHamiltonians.heisenberg(c) - # Time evolve forward: - ψ1 = tdvp(H, -0.1im, ψ0; root_vertex, nsweeps=1, cutoff, nsites=2) - @test norm(ψ1) ≈ 1.0 + H = ttn(os, s) - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 + rng = StableRNG(1234) + ψ0 = normalize(random_ttn(rng, s)) - # Average energy should be conserved: - @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) + # Time evolve forward: + ψ1 = tdvp(H, -0.1im, ψ0; root_vertex, nsweeps = 1, cutoff, nsites = 2) + @test norm(ψ1) ≈ 1.0 - # Time evolve backwards: - ψ2 = tdvp(H, +0.1im, ψ1; nsweeps=1, cutoff) + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 - @test norm(ψ2) ≈ 1.0 + # Average energy should be conserved: + @test real(inner(ψ1', H, ψ1)) ≈ inner(ψ0', H, ψ0) - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 - end + # Time evolve backwards: + ψ2 = tdvp(H, +0.1im, ψ1; nsweeps = 1, cutoff) - @testset "TDVP: Sum of Hamiltonians" begin - cutoff = 1e-10 + @test norm(ψ2) ≈ 1.0 - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - - os1 = OpSum() - for e in edges(c) - os1 += 0.5, "S+", src(e), "S-", dst(e) - os1 += 0.5, "S-", src(e), "S+", dst(e) - end - os2 = OpSum() - for e in edges(c) - os2 += "Sz", src(e), "Sz", dst(e) + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 end - H1 = ttn(os1, s) - H2 = ttn(os2, s) - Hs = [H1, H2] - - rng = StableRNG(1234) - ψ0 = normalize(random_ttn(rng, s; link_space=10)) - - ψ1 = tdvp(Hs, -0.1im, ψ0; nsweeps=1, cutoff, nsites=1) - - @test norm(ψ1) ≈ 1.0 + @testset "TDVP: Sum of Hamiltonians" begin + cutoff = 1.0e-10 - ## Should lose fidelity: - #@test abs(inner(ψ0,ψ1)) < 0.9 + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) - # Average energy should be conserved: - @test real(sum(H -> inner(ψ1', H, ψ1), Hs)) ≈ sum(H -> inner(ψ0', H, ψ0), Hs) + os1 = OpSum() + for e in edges(c) + os1 += 0.5, "S+", src(e), "S-", dst(e) + os1 += 0.5, "S-", src(e), "S+", dst(e) + end + os2 = OpSum() + for e in edges(c) + os2 += "Sz", src(e), "Sz", dst(e) + end - # Time evolve backwards: - ψ2 = tdvp(Hs, +0.1im, ψ1; nsweeps=1, cutoff) + H1 = ttn(os1, s) + H2 = ttn(os2, s) + Hs = [H1, H2] - @test norm(ψ2) ≈ 1.0 + rng = StableRNG(1234) + ψ0 = normalize(random_ttn(rng, s; link_space = 10)) - # Should rotate back to original state: - @test abs(inner(ψ0, ψ2)) > 0.99 - end + ψ1 = tdvp(Hs, -0.1im, ψ0; nsweeps = 1, cutoff, nsites = 1) - @testset "Accuracy Test" begin - tau = 0.1 - ttotal = 1.0 - cutoff = 1e-12 + @test norm(ψ1) ≈ 1.0 - tooth_lengths = fill(2, 3) - root_vertex = (3, 2) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) + ## Should lose fidelity: + #@test abs(inner(ψ0,ψ1)) < 0.9 - os = ModelHamiltonians.heisenberg(c) - H = ttn(os, s) - HM = contract(H) + # Average energy should be conserved: + @test real(sum(H -> inner(ψ1', H, ψ1), Hs)) ≈ sum(H -> inner(ψ0', H, ψ0), Hs) - Ut = exp(-im * tau * HM) + # Time evolve backwards: + ψ2 = tdvp(Hs, +0.1im, ψ1; nsweeps = 1, cutoff) - state = ttn(ComplexF64, v -> iseven(sum(isodd.(v))) ? "Up" : "Dn", s) - statex = contract(state) + @test norm(ψ2) ≈ 1.0 - Sz_tdvp = Float64[] - Sz_exact = Float64[] - - c = (2, 1) - Szc = op("Sz", s[c]) - - Nsteps = Int(ttotal / tau) - for step in 1:Nsteps - statex = noprime(Ut * statex) - statex /= norm(statex) - - state = tdvp( - H, - -im * tau, - state; - cutoff, - normalize=false, - updater_kwargs=(; tol=1e-12, maxiter=500, krylovdim=25), - ) - push!(Sz_tdvp, real(expect("Sz", state; vertices=[c])[c])) - push!(Sz_exact, real(scalar(dag(prime(statex, s[c])) * Szc * statex))) - F = abs(scalar(dag(statex) * contract(state))) + # Should rotate back to original state: + @test abs(inner(ψ0, ψ2)) > 0.99 end - @test norm(Sz_tdvp - Sz_exact) < 1e-5 - end - - # TODO: apply gates in ITensorNetworks - - @testset "TEBD Comparison" begin - cutoff = 1e-12 - maxdim = typemax(Int) - tau = 0.1 - ttotal = 1.0 - - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - - os = ModelHamiltonians.heisenberg(c) - H = ttn(os, s) - - gates = ITensor[] - for e in edges(c) - s1 = s[src(e)] - s2 = s[dst(e)] - hj = - op("Sz", s1) * op("Sz", s2) + - 1 / 2 * op("S+", s1) * op("S-", s2) + - 1 / 2 * op("S-", s1) * op("S+", s2) - Gj = exp(-1.0im * tau / 2 * hj) - push!(gates, Gj) - end - append!(gates, reverse(gates)) - - state = ttn(v -> iseven(sum(isodd.(v))) ? "Up" : "Dn", s) - phi = copy(state) - c = (2, 1) - - # - # Evolve using TEBD - # - - Nsteps = convert(Int, ceil(abs(ttotal / tau))) - Sz1 = zeros(Nsteps) - En1 = zeros(Nsteps) - Sz2 = zeros(Nsteps) - En2 = zeros(Nsteps) - - for step in 1:Nsteps - state = apply(gates, state; cutoff, maxdim) - - nsites = (step <= 3 ? 2 : 1) - phi = tdvp( - H, - -tau * im, - phi; - nsweeps=1, - cutoff, - nsites, - normalize=true, - updater_kwargs=(; krylovdim=15), - ) - - Sz1[step] = real(expect("Sz", state; vertices=[c])[c]) - Sz2[step] = real(expect("Sz", phi; vertices=[c])[c]) - En1[step] = real(inner(state', H, state)) - En2[step] = real(inner(phi', H, phi)) + @testset "Accuracy Test" begin + tau = 0.1 + ttotal = 1.0 + cutoff = 1.0e-12 + + tooth_lengths = fill(2, 3) + root_vertex = (3, 2) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) + + os = ModelHamiltonians.heisenberg(c) + H = ttn(os, s) + HM = contract(H) + + Ut = exp(-im * tau * HM) + + state = ttn(ComplexF64, v -> iseven(sum(isodd.(v))) ? "Up" : "Dn", s) + statex = contract(state) + + Sz_tdvp = Float64[] + Sz_exact = Float64[] + + c = (2, 1) + Szc = op("Sz", s[c]) + + Nsteps = Int(ttotal / tau) + for step in 1:Nsteps + statex = noprime(Ut * statex) + statex /= norm(statex) + + state = tdvp( + H, + -im * tau, + state; + cutoff, + normalize = false, + updater_kwargs = (; tol = 1.0e-12, maxiter = 500, krylovdim = 25), + ) + push!(Sz_tdvp, real(expect("Sz", state; vertices = [c])[c])) + push!(Sz_exact, real(scalar(dag(prime(statex, s[c])) * Szc * statex))) + F = abs(scalar(dag(statex) * contract(state))) + end + + @test norm(Sz_tdvp - Sz_exact) < 1.0e-5 end - # - # Evolve using TDVP - # - - phi = ttn(v -> iseven(sum(isodd.(v))) ? "Up" : "Dn", s) - obs = observer( - "Sz" => (; state) -> expect("Sz", state; vertices=[c])[c], - "En" => (; state) -> real(inner(state', H, state)), - ) - phi = tdvp( - H, - -im * ttotal, - phi; - time_step=-im * tau, - cutoff, - normalize=false, - (sweep_observer!)=obs, - root_vertex=(3, 2), - ) - - @test norm(Sz1 - Sz2) < 5e-3 - @test norm(En1 - En2) < 5e-3 - @test abs.(last(Sz1) - last(obs.Sz)) .< 5e-3 - @test abs.(last(Sz2) - last(obs.Sz)) .< 5e-3 - end - - @testset "Imaginary Time Evolution" for reverse_step in [true, false] - cutoff = 1e-12 - tau = 1.0 - ttotal = 50.0 - - tooth_lengths = fill(2, 3) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - - os = ModelHamiltonians.heisenberg(c) - H = ttn(os, s) - - rng = StableRNG(1234) - state = normalize(random_ttn(rng, s; link_space=2)) - - trange = 0.0:tau:ttotal - for (step, t) in enumerate(trange) - nsites = (step <= 10 ? 2 : 1) - state = tdvp( - H, - -tau, - state; - cutoff, - nsites, - reverse_step, - normalize=true, - updater_kwargs=(; krylovdim=15), - ) + # TODO: apply gates in ITensorNetworks + + @testset "TEBD Comparison" begin + cutoff = 1.0e-12 + maxdim = typemax(Int) + tau = 0.1 + ttotal = 1.0 + + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) + + os = ModelHamiltonians.heisenberg(c) + H = ttn(os, s) + + gates = ITensor[] + for e in edges(c) + s1 = s[src(e)] + s2 = s[dst(e)] + hj = + op("Sz", s1) * op("Sz", s2) + + 1 / 2 * op("S+", s1) * op("S-", s2) + + 1 / 2 * op("S-", s1) * op("S+", s2) + Gj = exp(-1.0im * tau / 2 * hj) + push!(gates, Gj) + end + append!(gates, reverse(gates)) + + state = ttn(v -> iseven(sum(isodd.(v))) ? "Up" : "Dn", s) + phi = copy(state) + c = (2, 1) + + # + # Evolve using TEBD + # + + Nsteps = convert(Int, ceil(abs(ttotal / tau))) + Sz1 = zeros(Nsteps) + En1 = zeros(Nsteps) + Sz2 = zeros(Nsteps) + En2 = zeros(Nsteps) + + for step in 1:Nsteps + state = apply(gates, state; cutoff, maxdim) + + nsites = (step <= 3 ? 2 : 1) + phi = tdvp( + H, + -tau * im, + phi; + nsweeps = 1, + cutoff, + nsites, + normalize = true, + updater_kwargs = (; krylovdim = 15), + ) + + Sz1[step] = real(expect("Sz", state; vertices = [c])[c]) + Sz2[step] = real(expect("Sz", phi; vertices = [c])[c]) + En1[step] = real(inner(state', H, state)) + En2[step] = real(inner(phi', H, phi)) + end + + # + # Evolve using TDVP + # + + phi = ttn(v -> iseven(sum(isodd.(v))) ? "Up" : "Dn", s) + obs = observer( + "Sz" => (; state) -> expect("Sz", state; vertices = [c])[c], + "En" => (; state) -> real(inner(state', H, state)), + ) + phi = tdvp( + H, + -im * ttotal, + phi; + time_step = -im * tau, + cutoff, + normalize = false, + (sweep_observer!) = obs, + root_vertex = (3, 2), + ) + + @test norm(Sz1 - Sz2) < 5.0e-3 + @test norm(En1 - En2) < 5.0e-3 + @test abs.(last(Sz1) - last(obs.Sz)) .< 5.0e-3 + @test abs.(last(Sz2) - last(obs.Sz)) .< 5.0e-3 end - @test inner(state', H, state) < -2.47 - end + @testset "Imaginary Time Evolution" for reverse_step in [true, false] + cutoff = 1.0e-12 + tau = 1.0 + ttotal = 50.0 + + tooth_lengths = fill(2, 3) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) + + os = ModelHamiltonians.heisenberg(c) + H = ttn(os, s) + + rng = StableRNG(1234) + state = normalize(random_ttn(rng, s; link_space = 2)) + + trange = 0.0:tau:ttotal + for (step, t) in enumerate(trange) + nsites = (step <= 10 ? 2 : 1) + state = tdvp( + H, + -tau, + state; + cutoff, + nsites, + reverse_step, + normalize = true, + updater_kwargs = (; krylovdim = 15), + ) + end + + @test inner(state', H, state) < -2.47 + end end end diff --git a/test/test_ttn_tdvp_time_dependent.jl b/test/test_ttn_tdvp_time_dependent.jl index 4101bc83..f1af8026 100644 --- a/test/test_ttn_tdvp_time_dependent.jl +++ b/test/test_ttn_tdvp_time_dependent.jl @@ -10,13 +10,13 @@ using OrdinaryDiffEqTsit5: Tsit5 using Test: @test, @test_broken, @testset include( - joinpath( - @__DIR__, "ITensorNetworksTestSolversUtils", "ITensorNetworksTestSolversUtils.jl" - ), + joinpath( + @__DIR__, "ITensorNetworksTestSolversUtils", "ITensorNetworksTestSolversUtils.jl" + ), ) using .ITensorNetworksTestSolversUtils: - ITensorNetworksTestSolversUtils, krylov_solver, ode_solver + ITensorNetworksTestSolversUtils, krylov_solver, ode_solver # Functions need to be defined in global scope (outside # of the @testset macro) @@ -25,212 +25,212 @@ using .ITensorNetworksTestSolversUtils: ω₂ = 0.2 ode_alg = Tsit5() -ode_kwargs = (; reltol=1e-8, abstol=1e-8) +ode_kwargs = (; reltol = 1.0e-8, abstol = 1.0e-8) ω⃗ = [ω₁, ω₂] f⃗ = [t -> cos(ω * t) for ω in ω⃗] -ode_updater_kwargs = (; f=[f⃗], solver_alg=ode_alg, ode_kwargs) +ode_updater_kwargs = (; f = [f⃗], solver_alg = ode_alg, ode_kwargs) function ode_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - internal_kwargs, - ode_kwargs, - solver_alg, - f, -) - region = first(sweep_plan[which_region_update]) - (; time_step, t) = internal_kwargs - t = isa(region, AbstractNamedEdge) ? t : t + time_step - - H⃗₀ = projected_operator![] - result, info = ode_solver( - -im * TimeDependentSum(f, H⃗₀), - time_step, - init; - current_time=t, - solver_alg, - ode_kwargs..., - ) - return result, (; info) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + internal_kwargs, + ode_kwargs, + solver_alg, + f, + ) + region = first(sweep_plan[which_region_update]) + (; time_step, t) = internal_kwargs + t = isa(region, AbstractNamedEdge) ? t : t + time_step + + H⃗₀ = projected_operator![] + result, info = ode_solver( + -im * TimeDependentSum(f, H⃗₀), + time_step, + init; + current_time = t, + solver_alg, + ode_kwargs..., + ) + return result, (; info) end function tdvp_ode_solver(H⃗₀, ψ₀; time_step, kwargs...) - psi_t, info = ode_solver( - -im * TimeDependentSum(f⃗, H⃗₀), time_step, ψ₀; solver_alg=ode_alg, ode_kwargs... - ) - return psi_t, (; info) + psi_t, info = ode_solver( + -im * TimeDependentSum(f⃗, H⃗₀), time_step, ψ₀; solver_alg = ode_alg, ode_kwargs... + ) + return psi_t, (; info) end -krylov_kwargs = (; tol=1e-8, krylovdim=15, eager=true) -krylov_updater_kwargs = (; f=[f⃗], krylov_kwargs) +krylov_kwargs = (; tol = 1.0e-8, krylovdim = 15, eager = true) +krylov_updater_kwargs = (; f = [f⃗], krylov_kwargs) function ITensorNetworksTestSolversUtils.krylov_solver( - H⃗₀, ψ₀; time_step, ishermitian=false, issymmetric=false, kwargs... -) - psi_t, info = krylov_solver( - -im * TimeDependentSum(f⃗, H⃗₀), - time_step, - ψ₀; - krylov_kwargs..., - ishermitian, - issymmetric, - ) - return psi_t, (; info) + H⃗₀, ψ₀; time_step, ishermitian = false, issymmetric = false, kwargs... + ) + psi_t, info = krylov_solver( + -im * TimeDependentSum(f⃗, H⃗₀), + time_step, + ψ₀; + krylov_kwargs..., + ishermitian, + issymmetric, + ) + return psi_t, (; info) end function krylov_updater( - init; - state!, - projected_operator!, - outputlevel, - which_sweep, - sweep_plan, - which_region_update, - internal_kwargs, - ishermitian=false, - issymmetric=false, - f, - krylov_kwargs, -) - (; time_step, t) = internal_kwargs - H⃗₀ = projected_operator![] - region = first(sweep_plan[which_region_update]) - t = isa(region, AbstractNamedEdge) ? t : t + time_step - - result, info = krylov_solver( - -im * TimeDependentSum(f, H⃗₀), - time_step, - init; - current_time=t, - krylov_kwargs..., - ishermitian, - issymmetric, - ) - return result, (; info) + init; + state!, + projected_operator!, + outputlevel, + which_sweep, + sweep_plan, + which_region_update, + internal_kwargs, + ishermitian = false, + issymmetric = false, + f, + krylov_kwargs, + ) + (; time_step, t) = internal_kwargs + H⃗₀ = projected_operator![] + region = first(sweep_plan[which_region_update]) + t = isa(region, AbstractNamedEdge) ? t : t + time_step + + result, info = krylov_solver( + -im * TimeDependentSum(f, H⃗₀), + time_step, + init; + current_time = t, + krylov_kwargs..., + ishermitian, + issymmetric, + ) + return result, (; info) end @testset "MPS: Time dependent Hamiltonian" begin - n = 4 - J₁ = 1.0 - J₂ = 0.1 - - time_step = 0.1 - time_total = 1.0 - - nsites = 2 - maxdim = 100 - cutoff = 1e-8 - - s = siteinds("S=1/2", n) - ℋ₁₀ = ModelHamiltonians.heisenberg(n; J1=J₁, J2=0.0) - ℋ₂₀ = ModelHamiltonians.heisenberg(n; J1=0.0, J2=J₂) - ℋ⃗₀ = [ℋ₁₀, ℋ₂₀] - H⃗₀ = [mpo(ℋ₀, s) for ℋ₀ in ℋ⃗₀] - - ψ₀ = complex(mps(j -> isodd(j) ? "↑" : "↓", s)) - - ψₜ_ode = tdvp( - H⃗₀, - time_total, - ψ₀; - time_step, - maxdim, - cutoff, - nsites, - updater=ode_updater, - updater_kwargs=ode_updater_kwargs, - ) - - ψₜ_krylov = tdvp( - H⃗₀, - time_total, - ψ₀; - time_step, - cutoff, - nsites, - updater=krylov_updater, - updater_kwargs=krylov_updater_kwargs, - ) - - ψₜ_full, _ = tdvp_ode_solver(contract.(H⃗₀), contract(ψ₀); time_step=time_total) - - @test norm(ψ₀) ≈ 1 - @test norm(ψₜ_ode) ≈ 1 - @test norm(ψₜ_krylov) ≈ 1 - @test norm(ψₜ_full) ≈ 1 - - ode_err = norm(contract(ψₜ_ode) - ψₜ_full) - krylov_err = norm(contract(ψₜ_krylov) - ψₜ_full) - #ToDo: Investigate why Krylov gives better result than ODE solver - @test_broken krylov_err > ode_err - @test ode_err < 1e-2 - @test krylov_err < 1e-2 + n = 4 + J₁ = 1.0 + J₂ = 0.1 + + time_step = 0.1 + time_total = 1.0 + + nsites = 2 + maxdim = 100 + cutoff = 1.0e-8 + + s = siteinds("S=1/2", n) + ℋ₁₀ = ModelHamiltonians.heisenberg(n; J1 = J₁, J2 = 0.0) + ℋ₂₀ = ModelHamiltonians.heisenberg(n; J1 = 0.0, J2 = J₂) + ℋ⃗₀ = [ℋ₁₀, ℋ₂₀] + H⃗₀ = [mpo(ℋ₀, s) for ℋ₀ in ℋ⃗₀] + + ψ₀ = complex(mps(j -> isodd(j) ? "↑" : "↓", s)) + + ψₜ_ode = tdvp( + H⃗₀, + time_total, + ψ₀; + time_step, + maxdim, + cutoff, + nsites, + updater = ode_updater, + updater_kwargs = ode_updater_kwargs, + ) + + ψₜ_krylov = tdvp( + H⃗₀, + time_total, + ψ₀; + time_step, + cutoff, + nsites, + updater = krylov_updater, + updater_kwargs = krylov_updater_kwargs, + ) + + ψₜ_full, _ = tdvp_ode_solver(contract.(H⃗₀), contract(ψ₀); time_step = time_total) + + @test norm(ψ₀) ≈ 1 + @test norm(ψₜ_ode) ≈ 1 + @test norm(ψₜ_krylov) ≈ 1 + @test norm(ψₜ_full) ≈ 1 + + ode_err = norm(contract(ψₜ_ode) - ψₜ_full) + krylov_err = norm(contract(ψₜ_krylov) - ψₜ_full) + #ToDo: Investigate why Krylov gives better result than ODE solver + @test_broken krylov_err > ode_err + @test ode_err < 1.0e-2 + @test krylov_err < 1.0e-2 end @testset "TTN: Time dependent Hamiltonian" begin - tooth_lengths = fill(2, 3) - root_vertex = (3, 2) - c = named_comb_tree(tooth_lengths) - s = siteinds("S=1/2", c) - - J₁ = 1.0 - J₂ = 0.1 - - time_step = 0.1 - time_total = 1.0 - - nsites = 2 - maxdim = 100 - cutoff = 1e-8 - - s = siteinds("S=1/2", c) - ℋ₁₀ = ModelHamiltonians.heisenberg(c; J1=J₁, J2=0.0) - ℋ₂₀ = ModelHamiltonians.heisenberg(c; J1=0.0, J2=J₂) - ℋ⃗₀ = [ℋ₁₀, ℋ₂₀] - H⃗₀ = [ttn(ℋ₀, s) for ℋ₀ in ℋ⃗₀] - - ψ₀ = ttn(ComplexF64, v -> iseven(sum(isodd.(v))) ? "↑" : "↓", s) - - ψₜ_ode = tdvp( - H⃗₀, - time_total, - ψ₀; - time_step, - maxdim, - cutoff, - nsites, - updater=ode_updater, - updater_kwargs=ode_updater_kwargs, - ) - - ψₜ_krylov = tdvp( - H⃗₀, - time_total, - ψ₀; - time_step, - cutoff, - nsites, - updater=krylov_updater, - updater_kwargs=krylov_updater_kwargs, - ) - ψₜ_full, _ = tdvp_ode_solver(contract.(H⃗₀), contract(ψ₀); time_step=time_total) - - @test norm(ψ₀) ≈ 1 - @test norm(ψₜ_ode) ≈ 1 - @test norm(ψₜ_krylov) ≈ 1 - @test norm(ψₜ_full) ≈ 1 - - ode_err = norm(contract(ψₜ_ode) - ψₜ_full) - krylov_err = norm(contract(ψₜ_krylov) - ψₜ_full) - #ToDo: Investigate why Krylov gives better result than ODE solver - @test_broken krylov_err > ode_err - @test ode_err < 1e-2 - @test krylov_err < 1e-2 + tooth_lengths = fill(2, 3) + root_vertex = (3, 2) + c = named_comb_tree(tooth_lengths) + s = siteinds("S=1/2", c) + + J₁ = 1.0 + J₂ = 0.1 + + time_step = 0.1 + time_total = 1.0 + + nsites = 2 + maxdim = 100 + cutoff = 1.0e-8 + + s = siteinds("S=1/2", c) + ℋ₁₀ = ModelHamiltonians.heisenberg(c; J1 = J₁, J2 = 0.0) + ℋ₂₀ = ModelHamiltonians.heisenberg(c; J1 = 0.0, J2 = J₂) + ℋ⃗₀ = [ℋ₁₀, ℋ₂₀] + H⃗₀ = [ttn(ℋ₀, s) for ℋ₀ in ℋ⃗₀] + + ψ₀ = ttn(ComplexF64, v -> iseven(sum(isodd.(v))) ? "↑" : "↓", s) + + ψₜ_ode = tdvp( + H⃗₀, + time_total, + ψ₀; + time_step, + maxdim, + cutoff, + nsites, + updater = ode_updater, + updater_kwargs = ode_updater_kwargs, + ) + + ψₜ_krylov = tdvp( + H⃗₀, + time_total, + ψ₀; + time_step, + cutoff, + nsites, + updater = krylov_updater, + updater_kwargs = krylov_updater_kwargs, + ) + ψₜ_full, _ = tdvp_ode_solver(contract.(H⃗₀), contract(ψ₀); time_step = time_total) + + @test norm(ψ₀) ≈ 1 + @test norm(ψₜ_ode) ≈ 1 + @test norm(ψₜ_krylov) ≈ 1 + @test norm(ψₜ_full) ≈ 1 + + ode_err = norm(contract(ψₜ_ode) - ψₜ_full) + krylov_err = norm(contract(ψₜ_krylov) - ψₜ_full) + #ToDo: Investigate why Krylov gives better result than ODE solver + @test_broken krylov_err > ode_err + @test ode_err < 1.0e-2 + @test krylov_err < 1.0e-2 end end diff --git a/test/test_ttno.jl b/test/test_ttno.jl index 8d147339..23122aab 100644 --- a/test/test_ttno.jl +++ b/test/test_ttno.jl @@ -8,33 +8,33 @@ using Random: shuffle using StableRNGs: StableRNG using Test: @test, @testset @testset "TTN operator Basics" begin - # random comb tree - rng = StableRNG(1234) - tooth_lengths = rand(rng, 1:3, rand(rng, 2:4)) - c = named_comb_tree(tooth_lengths) - # specify random site dimension on every site - dmap = v -> rand(rng, 1:3) - is = siteinds(dmap, c) - # operator site inds - is_isp = union_all_inds(is, prime(is; links=[])) - # specify random linear vertex ordering of graph vertices - vertex_order = shuffle(rng, collect(vertices(c))) - @testset "Construct TTN operator from ITensor or Array" begin - cutoff = 1e-10 - sites_o = [is_isp[v] for v in vertex_order] - # create random ITensor with these indices + # random comb tree rng = StableRNG(1234) - O = random_itensor(rng, sites_o...) - # dense TTN constructor from IndsNetwork - @disable_warn_order o1 = ttn(O, is_isp; cutoff) - root_vertex = only(ortho_region(o1)) - @disable_warn_order begin - O1 = contract(o1, root_vertex) + tooth_lengths = rand(rng, 1:3, rand(rng, 2:4)) + c = named_comb_tree(tooth_lengths) + # specify random site dimension on every site + dmap = v -> rand(rng, 1:3) + is = siteinds(dmap, c) + # operator site inds + is_isp = union_all_inds(is, prime(is; links = [])) + # specify random linear vertex ordering of graph vertices + vertex_order = shuffle(rng, collect(vertices(c))) + @testset "Construct TTN operator from ITensor or Array" begin + cutoff = 1.0e-10 + sites_o = [is_isp[v] for v in vertex_order] + # create random ITensor with these indices + rng = StableRNG(1234) + O = random_itensor(rng, sites_o...) + # dense TTN constructor from IndsNetwork + @disable_warn_order o1 = ttn(O, is_isp; cutoff) + root_vertex = only(ortho_region(o1)) + @disable_warn_order begin + O1 = contract(o1, root_vertex) + end + @test norm(O - O1) < 1.0e2 * cutoff + end + @testset "Ortho" begin + # TODO end - @test norm(O - O1) < 1e2 * cutoff - end - @testset "Ortho" begin - # TODO - end end end diff --git a/test/test_ttns.jl b/test/test_ttns.jl index 5eafce71..7e620b69 100644 --- a/test/test_ttns.jl +++ b/test/test_ttns.jl @@ -9,32 +9,32 @@ using Random: shuffle using StableRNGs: StableRNG using Test: @test, @testset @testset "TTN Basics" begin - # random comb tree - rng = StableRNG(1234) - tooth_lengths = rand(rng, 1:3, rand(rng, 2:4)) - c = named_comb_tree(tooth_lengths) - # specify random site dimension on every site - dmap = v -> rand(rng, 1:3) - is = siteinds(dmap, c) - # specify random linear vertex ordering of graph vertices - vertex_order = shuffle(rng, collect(vertices(c))) - - @testset "Construct TTN from ITensor or Array" begin - cutoff = 1e-10 - # create random ITensor with these indices + # random comb tree rng = StableRNG(1234) - S = random_itensor(rng, vertex_data(is)...) - # dense TTN constructor from IndsNetwork - @disable_warn_order s1 = ttn(S, is; cutoff) - root_vertex = only(ortho_region(s1)) - @disable_warn_order begin - S1 = contract(s1, root_vertex) + tooth_lengths = rand(rng, 1:3, rand(rng, 2:4)) + c = named_comb_tree(tooth_lengths) + # specify random site dimension on every site + dmap = v -> rand(rng, 1:3) + is = siteinds(dmap, c) + # specify random linear vertex ordering of graph vertices + vertex_order = shuffle(rng, collect(vertices(c))) + + @testset "Construct TTN from ITensor or Array" begin + cutoff = 1.0e-10 + # create random ITensor with these indices + rng = StableRNG(1234) + S = random_itensor(rng, vertex_data(is)...) + # dense TTN constructor from IndsNetwork + @disable_warn_order s1 = ttn(S, is; cutoff) + root_vertex = only(ortho_region(s1)) + @disable_warn_order begin + S1 = contract(s1, root_vertex) + end + @test norm(S - S1) < 1.0e2 * cutoff end - @test norm(S - S1) < 1e2 * cutoff - end - @testset "Ortho" begin - # TODO - end + @testset "Ortho" begin + # TODO + end end end