From e0dd1bbe21cacfb10fd865c93f95328dbd876923 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 7 May 2026 18:40:39 -0400 Subject: [PATCH 1/6] [BREAKING] Drop IndsNetwork/graph ITensorNetwork ctors and ttn/mps generic ctors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the IndsNetwork-, AbstractNamedGraph-, and AbstractSimpleGraph-based ITensorNetwork constructor families (state/value, function-callback, undef, plain-graph variants), the broken-stub ITensorNetwork{V}(g::NamedGraph), and the generic ttn(args...)/mps(args...)/mps(f, ::Vector{<:Index})/ mps(::Vector{<:Index}) wrappers that delegated to them. Also remove visualize(::IndsNetwork). Internal callers that built `ITensorNetwork(::IndsNetwork)`-style placeholders now build `ITensorNetwork(tensors, graph::NamedGraph)` directly: bilinearform- network, the directsum branch in abstracttreetensornetwork, opsum_to_ttn, and ttn(::ITensor, ::IndsNetwork). set_ortho_region and the QR/SVD-decomposition ttn use TreeTensorNetwork(tn) instead of ttn(tn::ITensorNetwork). Bug fix: trivial_space was used in abstractindsnetwork.jl and abstractitensornetwork.jl without being imported from ITensorsExtensions — imports added. test/utils.jl: rename state_tensornetwork → tensornetworkstate, drop default_tensornetwork (callers use random_tensornetwork for structural setups), and switch tensornetworkstate / random_tensornetwork to construct via ITensorNetwork(tensors, graph) so graph edge order is preserved. Test files rewritten to use the new helpers and the surviving ctor surface; the exhaustive parameterised "Product state constructors" testset (which covered the deleted state-arg ctor surface) is dropped. Docs updated: itensor_networks.md and tree_tensor_networks.md show the canonical (tensors, graph) construction; deprecated_methods.md entries for the removed ctors are removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- Project.toml | 2 +- docs/Project.toml | 2 +- docs/src/computing_properties.md | 11 +- docs/src/deprecated_methods.md | 37 ---- docs/src/interface_methods.md | 14 +- docs/src/itensor_networks.md | 45 ++--- docs/src/solvers.md | 5 +- docs/src/tree_tensor_networks.md | 49 ++--- examples/Project.toml | 2 +- src/abstractindsnetwork.jl | 2 +- src/abstractitensornetwork.jl | 2 +- src/formnetworks/bilinearformnetwork.jl | 8 +- src/indsnetwork.jl | 12 -- src/itensornetwork.jl | 187 +----------------- .../abstracttreetensornetwork.jl | 17 +- .../opsum_to_ttn/opsum_to_ttn.jl | 12 +- src/treetensornetworks/treetensornetwork.jl | 82 ++------ test/Project.toml | 2 +- test/solvers/test_applyexp.jl | 9 +- test/solvers/test_eigsolve.jl | 5 +- test/test_additensornetworks.jl | 11 +- test/test_expect.jl | 2 +- test/test_itensornetwork.jl | 108 ++-------- test/test_tebd.jl | 2 +- test/test_ttn_expect.jl | 5 +- test/test_ttn_position.jl | 12 +- test/test_ttns.jl | 11 +- test/utils.jl | 112 ++++++++++- 28 files changed, 258 insertions(+), 510 deletions(-) diff --git a/Project.toml b/Project.toml index 0d38933e..320afd51 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorNetworks" uuid = "2919e153-833c-4bdc-8836-1ea460a35fc7" -version = "0.21.0" +version = "0.22.0" authors = ["Matthew Fishman , Joseph Tindall and contributors"] [workspace] diff --git a/docs/Project.toml b/docs/Project.toml index 35064770..bf58fae3 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -16,7 +16,7 @@ path = ".." Documenter = "1.10" Graphs = "1" ITensorFormatter = "0.2.27" -ITensorNetworks = "0.21" +ITensorNetworks = "0.22" ITensors = "0.9" Literate = "2.20.1" NamedGraphs = "0.11" diff --git a/docs/src/computing_properties.md b/docs/src/computing_properties.md index f2b221ed..71396880 100644 --- a/docs/src/computing_properties.md +++ b/docs/src/computing_properties.md @@ -2,7 +2,8 @@ ```@setup main using Graphs: edges, vertices -using ITensorNetworks: ITensorNetwork, expect, inner, loginner, normalize, siteinds, ttn +using ITensorNetworks: + ITensorNetwork, TreeTensorNetwork, expect, inner, loginner, normalize, siteinds using ITensors: Index, random_itensor using LinearAlgebra: norm using NamedGraphs.GraphsExtensions: incident_edges @@ -20,10 +21,10 @@ end g = named_grid((4,)) s = siteinds("S=1/2", g) -phi = normalize(ttn(random_state(g, s; link_space = 2))) -psi = normalize(ttn(random_state(g, s; link_space = 2))) -x = normalize(ttn(random_state(g, s; link_space = 2))) -y = normalize(ttn(random_state(g, s; link_space = 2))) +phi = normalize(TreeTensorNetwork(random_state(g, s; link_space = 2))) +psi = normalize(TreeTensorNetwork(random_state(g, s; link_space = 2))) +x = normalize(TreeTensorNetwork(random_state(g, s; link_space = 2))) +y = normalize(TreeTensorNetwork(random_state(g, s; link_space = 2))) v = first(vertices(psi)) ``` diff --git a/docs/src/deprecated_methods.md b/docs/src/deprecated_methods.md index 48dd64bc..35a0b5dc 100644 --- a/docs/src/deprecated_methods.md +++ b/docs/src/deprecated_methods.md @@ -2,43 +2,6 @@ Suggestions of methods which could be deleted. -## ITensorNetwork Methods - -#### ITensorNetwork Constructors - -* From a named graph, forwards to construction from `IndsNetwork` (`itensornetwork.jl`): - ```julia - ITensorNetwork{V}(g::NamedGraph) - ITensorNetwork(eltype::Type, undef::UndefInitializer, graph::AbstractNamedGraph; kws...) - ITensorNetwork(f, graph::AbstractNamedGraph; kwargs...) - ITensorNetwork(graph::AbstractNamedGraph; kwargs...) - ``` - -* From a simple graph, forwards to construction from `IndsNetwork` (`itensornetwork.jl`): - ```julia - ITensorNetwork(eltype::Type, undef::UndefInitializer, graph::AbstractSimpleGraph; kws...) - ITensorNetwork(f, graph::AbstractSimpleGraph; kwargs...) - ITensorNetwork(graph::AbstractSimpleGraph; kwargs...) - ``` - -* From a function over vertices or from a "value" (e.g. a string like `"Up"`, - an `Op`, an array, or a per-vertex dict/array) that is converted to a callable and used - to initialize each vertex tensor (`itensornetwork.jl`): - ```julia - ITensorNetwork(value, is::IndsNetwork; kwargs...) - ITensorNetwork(elt::Type, f, is::IndsNetwork; link_space = trivial_space(is), kws...) - ITensorNetwork(itensor_constructor::Function, is::IndsNetwork; link_space = trivial_space(is), kwargs...) - ``` - -* Construct an `ITensorNetwork` from an `IndsNetwork`. Initializes ITensors with `undef` storage on each vertex - of the `IndsNetwork` with the corresponding indices (`itensornetwork.jl`): - ```julia - ITensorNetwork(eltype::Type, undef::UndefInitializer, is::IndsNetwork; kwargs...) - ITensorNetwork(eltype::Type, is::IndsNetwork; kwargs...) - ITensorNetwork(undef::UndefInitializer, is::IndsNetwork; kwargs...) - ITensorNetwork(is::IndsNetwork; kwargs...) - ``` - ## Global Operations on ITensorNetworks diff --git a/docs/src/interface_methods.md b/docs/src/interface_methods.md index 2c40c497..7eb40df5 100644 --- a/docs/src/interface_methods.md +++ b/docs/src/interface_methods.md @@ -6,20 +6,20 @@ Recommended methods for building applications on top of ITensorNetworks. These ITensorNetwork constructor interfaces are foundational to other constructors: -* From dictionary-like objects, including other `ITensorNetwork` objects, or a `Dict` - from vertices to `ITensor`. The `tensors` can be a collection of `ITensor`s in which case - the vertex labels are auto-assigned to `eachindex(tensors)` and edges are inferred - from shared indices between the `ITensor`s. +* From a collection of `ITensor`s indexed by vertex (a `Dict`, `Dictionary`, or + `Vector{ITensor}` with linear-index vertex labels). Edges are inferred from shared + `Index` objects between the tensors. ```julia # `keys(tensors)` are vertices, `values(tensors)` are tensors on those vertices ITensorNetwork(tensors) ITensorNetwork{V}(tensors) ``` -* From collections of vertices and tensors. E.g. a `Vector{Int}` and a `Vector{ITensor}`. +* From a collection of `ITensor`s placed at the vertices of a given `NamedGraph`. No + edge inference; the graph's edges are used as-is. ```julia - ITensorNetwork(vertices, tensors) - ITensorNetwork{V}(vertices, tensors) + ITensorNetwork(tensors, graph::NamedGraph) + ITensorNetwork{V}(tensors, graph::NamedGraph) ``` diff --git a/docs/src/itensor_networks.md b/docs/src/itensor_networks.md index 7b225c1f..1961f6e6 100644 --- a/docs/src/itensor_networks.md +++ b/docs/src/itensor_networks.md @@ -11,17 +11,13 @@ Key facts: - The underlying graph is a [`NamedGraph`](https://github.com/ITensor/NamedGraphs.jl), so vertices can be any hashable Julia value: integers, tuples, strings, etc. - Each vertex holds exactly one `ITensor`. -- Edges and link indices are either inferred from shared `Index` objects (when constructing - from a collection of `ITensor`s) or inserted automatically (when constructing from an - `IndsNetwork`). +- Edges are either inferred from shared `Index` objects (when constructing from a + collection of `ITensor`s) or taken from a graph passed explicitly alongside the tensors. ## Construction -The most common entry point is an `IndsNetwork` — a graph whose vertices and edges carry -`Index` objects. Generate site indices with the `siteinds` function which takes a site -type string (such as "S=1/2" or "Electron") and a NamedGraph. The NamedGraph can be -generated from functions such as `named_grid`, `named_comb_tree`, etc. from the NamedGraphs.jl -`NamedGraphGenerators` module: +When you already have `ITensor`s in hand, edges are inferred automatically from shared +indices: ```@example main using Graphs: edges, ne, neighbors, nv, vertices @@ -29,31 +25,30 @@ using ITensorNetworks: ITensorNetwork, add, linkinds, siteinds using ITensors: Index, ITensor using NamedGraphs.NamedGraphGenerators: named_grid -# 3×3 square-lattice tensor network -g = named_grid((3, 3)) -s = siteinds("S=1/2", g) # one spin-½ Index per vertex - -# Zero-initialized, bond dimension 2 -ψ = ITensorNetwork(s; link_space = 2) - -# Product state — every site in the |↑⟩ state -ψ = ITensorNetwork("Up", s) +i, j, k = Index(2, "i"), Index(2, "j"), Index(2, "k") +A, B, C = ITensor(i, j), ITensor(j, k), ITensor(k) -# Staggered initialization with a vertex-dependent function -ψ = ITensorNetwork(v -> isodd(sum(v)) ? "Up" : "Dn", s) +tn = ITensorNetwork([A, B, C]) # integer vertices 1, 2, 3 +tn = ITensorNetwork(Dict("A" => A, "B" => B, "C" => C)) # named vertices via a Dict ``` -When you already have `ITensor`s in hand, edges are inferred automatically from shared -indices: +If you want to control edges directly — for example to build an empty network on a +prescribed lattice and fill in tensors later — pass a `NamedGraph` along with a +collection of `ITensor`s indexed by vertex: ```@example main -i, j, k = Index(2, "i"), Index(2, "j"), Index(2, "k") -A, B, C = ITensor(i, j), ITensor(j, k), ITensor(k) +g = named_grid((3, 3)) +s = siteinds("S=1/2", g) # one spin-½ Index per vertex -tn = ITensorNetwork([A, B, C]) # integer vertices 1, 2, 3 -tn = ITensorNetwork(Dict("A" => A, "B" => B, "C" => C)) # named vertices via a Dict +# Build site tensors on the 3×3 lattice with one (placeholder) site index each +tensors = Dict(v => ITensor(s[v]...) for v in vertices(g)) +ψ = ITensorNetwork(tensors, g) ``` +Higher-level construction routines (random networks, product states, OpSum-derived +TTNs, etc.) are provided by sibling functions like `ttn(opsum, sites)` and the test-only +helpers in `test/utils.jl`. + ```@docs; canonical=false ITensorNetworks.ITensorNetwork ``` diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 34f50c2c..6a9b7218 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -17,7 +17,8 @@ variational sweep algorithm. ```@example main using Graphs: vertices -using ITensorNetworks: ITensorNetwork, dmrg, dst, edges, normalize, siteinds, src, ttn +using ITensorNetworks: + ITensorNetwork, TreeTensorNetwork, dmrg, dst, edges, normalize, siteinds, src, ttn using ITensors: Index, OpSum, random_itensor using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs.NamedGraphGenerators: named_comb_tree @@ -45,7 +46,7 @@ H = let h = OpSum() end # Random initial state (normalise first!) -psi0 = normalize(ttn(random_state(g, s; link_space = 2))) +psi0 = normalize(TreeTensorNetwork(random_state(g, s; link_space = 2))) # Run DMRG energy, psi = dmrg(H, psi0; diff --git a/docs/src/tree_tensor_networks.md b/docs/src/tree_tensor_networks.md index 52ed2231..b42ec9b2 100644 --- a/docs/src/tree_tensor_networks.md +++ b/docs/src/tree_tensor_networks.md @@ -11,48 +11,49 @@ records which vertices currently form the orthogonality center of the network. A update this field as the gauge changes. **MPS** (matrix product states) are the special case of a `TreeTensorNetwork` on a -1D path graph. The [`mps`](@ref ITensorNetworks.mps) constructor enforces this topology -and provides a convenient interface for 1D calculations. +1D path graph. ## Construction -### From an `IndsNetwork` or graph +### From an `OpSum` (Hamiltonian) + +A common way to obtain a Hamiltonian-shaped TTN is to convert an `OpSum` over an +`IndsNetwork` of site indices. + +```@docs; canonical=false +ITensorNetworks.ttn(::ITensors.Ops.OpSum, ::ITensorNetworks.IndsNetwork) +``` + +### From an existing `ITensorNetwork` + +The `TreeTensorNetwork` struct wraps an `ITensorNetwork` and records the current +orthogonality region. Use the `TreeTensorNetwork` constructor to convert a plain +`ITensorNetwork` with tree topology into a `TTN`, and `ITensorNetwork` to strip the +gauge metadata when you need a plain network again. ```@example main using Graphs: vertices -using ITensorNetworks: ITensorNetwork, TreeTensorNetwork, mps, ortho_region, orthogonalize, +using ITensorNetworks: ITensorNetwork, TreeTensorNetwork, ortho_region, orthogonalize, siteinds, ttn -using ITensors: ITensors +using ITensors: ITensors, ITensor using LinearAlgebra: norm +using NamedGraphs: NamedGraph using NamedGraphs.NamedGraphGenerators: named_comb_tree # Comb-tree TTN (a popular tree topology for 2D-like systems) g = named_comb_tree((3, 2)) sites = siteinds("S=1/2", g) -psi = ttn(sites) # zero-initialised -psi = ttn(v -> "Up", sites) # product state - -# 1D MPS -s1d = siteinds("S=1/2", 6) -mps_state = mps(v -> "Up", s1d) # product MPS +# Build an `ITensorNetwork` from explicit per-vertex tensors and wrap as a TTN +tensors = Dict(v => ITensor(sites[v]...) for v in vertices(g)) +itn = ITensorNetwork(tensors, NamedGraph(g)) +psi = TreeTensorNetwork(itn) ``` -```@docs; canonical=false -ITensorNetworks.ttn -ITensorNetworks.mps -``` - -### The `TreeTensorNetwork` type and conversion - -The `TreeTensorNetwork` struct wraps an `ITensorNetwork` and records the current -orthogonality region. Use the `TreeTensorNetwork` constructor to convert a plain -`ITensorNetwork` with tree topology into a `TTN`, and `ITensorNetwork` to strip the -gauge metadata when you need a plain network again. +To strip the gauge metadata back to a plain `ITensorNetwork`: ```@example main -itn = ITensorNetwork(psi) # TTN → ITensorNetwork -psi = TreeTensorNetwork(itn) # ITensorNetwork → TTN +itn_again = ITensorNetwork(psi) # TTN → ITensorNetwork ``` ```@docs; canonical=false diff --git a/examples/Project.toml b/examples/Project.toml index 3a9411e4..7899d300 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -5,4 +5,4 @@ ITensorNetworks = "2919e153-833c-4bdc-8836-1ea460a35fc7" path = ".." [compat] -ITensorNetworks = "0.21" +ITensorNetworks = "0.22" diff --git a/src/abstractindsnetwork.jl b/src/abstractindsnetwork.jl index a5799703..49af846e 100644 --- a/src/abstractindsnetwork.jl +++ b/src/abstractindsnetwork.jl @@ -1,4 +1,4 @@ -using .ITensorsExtensions: ITensorsExtensions, promote_indtype +using .ITensorsExtensions: ITensorsExtensions, promote_indtype, trivial_space using DataGraphs: DataGraphs, AbstractDataGraph, DataGraph, IsUnderlyingGraph, edge_data, get_edge_data, get_vertex_data, is_edge_assigned, is_vertex_assigned, map_data, set_edge_data!, set_vertex_data!, underlying_graph_type, vertex_data diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index 11f91c96..d5382d02 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -1,4 +1,4 @@ -using .ITensorsExtensions: ITensorsExtensions, indtype, promote_indtype +using .ITensorsExtensions: ITensorsExtensions, indtype, promote_indtype, trivial_space using Adapt: Adapt, adapt, adapt_structure using DataGraphs: DataGraphs, edge_data, get_vertex_data, is_vertex_assigned, set_vertex_data!, underlying_graph, underlying_graph_type, vertex_data diff --git a/src/formnetworks/bilinearformnetwork.jl b/src/formnetworks/bilinearformnetwork.jl index e0bc24aa..4c632c1a 100644 --- a/src/formnetworks/bilinearformnetwork.jl +++ b/src/formnetworks/bilinearformnetwork.jl @@ -84,14 +84,16 @@ function BilinearFormNetwork( bra_site_inds = mapreduce(v -> siteinds(bra, v), vcat, vertices(bra); init = Index[]) ket_site_inds = mapreduce(v -> siteinds(ket, v), vcat, vertices(ket); init = Index[]) @assert issetequal(bra_site_inds, ket_site_inds) - link_space = isempty(bra_site_inds) ? 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]) + g = NamedGraph(underlying_graph(operator_inds)) + ts = Dict{vertextype(g), ITensor}() + for v in vertices(operator_inds) + ts[v] = itensor_identity_map(scalartype(ket), s[v] .=> s_mapped[v]) end + O = ITensorNetwork(ts, g) O = adapt(promote_type(datatype(bra), datatype(ket)), O) return BilinearFormNetwork(O, bra, ket; dual_site_index_map, kwargs...) end diff --git a/src/indsnetwork.jl b/src/indsnetwork.jl index c7aadb3e..c7f6c851 100644 --- a/src/indsnetwork.jl +++ b/src/indsnetwork.jl @@ -302,15 +302,3 @@ 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) end - -# -# Visualization -# - -# TODO: Move to an `ITensorNetworksVisualizationInterfaceExt` -# package extension (and define a `VisualizationInterface` package -# based on `ITensorVisualizationCore`.). -using ITensors.ITensorVisualizationCore: ITensorVisualizationCore, visualize -function ITensorVisualizationCore.visualize(is::IndsNetwork, args...; kwargs...) - return visualize(ITensorNetwork(is), args...; kwargs...) -end diff --git a/src/itensornetwork.jl b/src/itensornetwork.jl index 7073c5da..00c7f6c6 100644 --- a/src/itensornetwork.jl +++ b/src/itensornetwork.jl @@ -1,7 +1,5 @@ -using .ITensorsExtensions: trivial_space using DataGraphs: DataGraphs, DataGraph -using Dictionaries: Indices -using ITensors: ITensors, ITensor, op +using ITensors: ITensors, ITensor using NamedGraphs: NamedGraphs, NamedEdge, NamedGraph, similar_graph, vertextype """ @@ -29,38 +27,14 @@ ITensorNetwork(tensors) ITensorNetwork(tensors, graph::NamedGraph) ``` -**From an `IndsNetwork`** (most common in legacy code): - -```julia -ITensorNetwork(is::IndsNetwork; link_space = 1) -ITensorNetwork(f, is::IndsNetwork; link_space = 1) -ITensorNetwork(eltype, undef, is::IndsNetwork; link_space = 1) -``` - - - With no function argument `f`, tensors are initialized to zero. - - With a function `f(v)` that returns a state label (e.g. `"Up"`, `"Dn"`) or - an `ITensor` constructor, tensors are initialized accordingly. - - `link_space` controls the bond-index dimension (default 1). - -**From a graph (site indices inferred as trivial):** - -```julia -ITensorNetwork(graph::AbstractNamedGraph; link_space = 1) -ITensorNetwork(f, graph::AbstractNamedGraph; link_space = 1) -``` - # Example ```jldoctest -julia> using NamedGraphs.NamedGraphGenerators: named_grid +julia> using ITensors: Index, ITensor -julia> g = named_grid((4,)); +julia> i, j, k = Index(2, "i"), Index(2, "j"), Index(2, "k"); -julia> s = siteinds("S=1/2", g); - -julia> tn = ITensorNetwork(s; link_space = 2); - -julia> tn = ITensorNetwork("Up", s); +julia> tn = ITensorNetwork([ITensor(i, j), ITensor(j, k)]); ``` @@ -147,9 +121,6 @@ function ITensorNetwork{V}(tn::ITensorNetwork) where {V} tensors = Dict{V, ITensor}(v => tn[v] for v in vertices(tn)) return ITensorNetwork(tensors, g) end -function ITensorNetwork{V}(g::NamedGraph) where {V} - return ITensorNetwork(NamedGraph{V}(g)) -end ITensorNetwork(tn::ITensorNetwork) = copy(tn) @@ -167,153 +138,3 @@ function NamedGraphs.similar_graph(tn::ITensorNetwork, underlying_graph::Abstrac default = Dict{vertextype(g), ITensor}(v => ITensor() for v in vertices(g)) return ITensorNetwork(default, g) end - -# -# Construction from underyling named graph -# - -function ITensorNetwork( - 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...)) -end - -function ITensorNetwork(graph::AbstractNamedGraph; kwargs...) - return ITensorNetwork(IndsNetwork(graph; kwargs...)) -end - -# -# Construction from underyling simple graph -# - -function ITensorNetwork( - 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...) -end - -function ITensorNetwork(graph::AbstractSimpleGraph; kwargs...) - return ITensorNetwork(IndsNetwork(graph); kwargs...) -end - -# -# Construction from IndsNetwork -# - -function ITensorNetwork(eltype::Type, undef::UndefInitializer, is::IndsNetwork; kwargs...) - 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 -end - -function ITensorNetwork(undef::UndefInitializer, is::IndsNetwork; kwargs...) - 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 -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)) -end - -function generic_state(f, inds::Vector) - return f(inds) -end -function generic_state(a::AbstractArray, inds::Vector) - 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)) -end -function generic_state(s::AbstractString, inds::NamedTuple) - # TODO: Figure out what to do if there is more than one site. - site_tensors = [ITensors.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`, -# try merging the two. -to_callable(value::Type) = value -to_callable(value::Function) = value -function to_callable(value::AbstractDict) - return Base.Fix1(getindex, value) ∘ keytype(value) -end -function to_callable(value::AbstractDictionary) - return Base.Fix1(getindex, value) ∘ keytype(value) -end -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...) -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 -end - -function ITensorNetwork( - itensor_constructor::Function, is::IndsNetwork; link_space = trivial_space(is), - kwargs... - ) - is = insert_linkinds(is; link_space) - tn = ITensorNetwork{vertextype(is)}(ITensor[]) - 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 diff --git a/src/treetensornetworks/abstracttreetensornetwork.jl b/src/treetensornetworks/abstracttreetensornetwork.jl index 8aa93cc4..85ebfa18 100644 --- a/src/treetensornetworks/abstracttreetensornetwork.jl +++ b/src/treetensornetworks/abstracttreetensornetwork.jl @@ -1,9 +1,11 @@ +using DataGraphs: underlying_graph using Graphs: has_vertex -using ITensors: ITensors, @Algorithm_str, Algorithm, directsum, hasinds, permute, plev +using ITensors: + ITensors, @Algorithm_str, Algorithm, ITensor, directsum, hasinds, permute, plev using IsApprox: IsApprox, Approx using NamedGraphs.GraphsExtensions: GraphsExtensions, a_star, edge_path, leaf_vertices, - post_order_dfs_edges, post_order_dfs_vertices -using NamedGraphs: namedgraph_a_star, steiner_tree + post_order_dfs_edges, post_order_dfs_vertices, vertextype +using NamedGraphs: NamedGraph, namedgraph_a_star, steiner_tree using TupleTools: TupleTools abstract type AbstractTreeTensorNetwork{V} <: AbstractITensorNetwork{V} end @@ -271,8 +273,13 @@ function Base.:+( tns[j] = orthogonalize(tns[j], root_vertex) end - # Output state - tn = ttn(siteinds(tns[1])) + # Output state: empty TTN over the same graph as the inputs. + # Tensor data and link indices are filled in by the directsum loop below. + g_out = NamedGraph(underlying_graph(siteinds(tns[1]))) + tensors_out = Dict{vertextype(g_out), ITensor}( + v => ITensor() for v in vertices(g_out) + ) + tn = TreeTensorNetwork(ITensorNetwork(tensors_out, g_out)) vs = post_order_dfs_vertices(tn, root_vertex) es = post_order_dfs_edges(tn, root_vertex) diff --git a/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl b/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl index 2f530858..ad00bba2 100644 --- a/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl +++ b/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl @@ -1,12 +1,13 @@ #using FillArrays: OneElement -#using DataGraphs: DataGraph +using DataGraphs: underlying_graph using Graphs: degree, is_tree, rem_vertex! using ITensors.LazyApply: Prod, Sum, coefficient using ITensors.NDTensors: Block, blockdim, maxdim, nblocks, nnzblocks, truncate! using ITensors.Ops: Op, OpSum, argument, coefficient, name, params, site, terms, which_op -using ITensors: flux, has_fermion_string, itensor, removeqns, space +using ITensors: ITensor, flux, has_fermion_string, itensor, removeqns, space using NamedGraphs.GraphsExtensions: - GraphsExtensions, boundary_edges, degrees, is_leaf_vertex, vertex_path + GraphsExtensions, boundary_edges, degrees, is_leaf_vertex, vertex_path, vertextype +using NamedGraphs: NamedGraph using StaticArrays: MVector # @@ -289,7 +290,10 @@ function compress_ttn( ) end - H = ttn(sites0) # initialize TTN without the dummy indices added + # initialize TTN without the dummy indices added; tensors are filled in below + g0 = NamedGraph(underlying_graph(sites0)) + tensors0 = Dict{vertextype(g0), ITensor}(v => ITensor() for v in vertices(g0)) + H = TreeTensorNetwork(ITensorNetwork(tensors0, g0)) function qnblock(i::Index, q::QN) for b in 2:(nblocks(i) - 1) flux(i, Block(b)) == q && return b diff --git a/src/treetensornetworks/treetensornetwork.jl b/src/treetensornetworks/treetensornetwork.jl index 48103384..0f2843de 100644 --- a/src/treetensornetworks/treetensornetwork.jl +++ b/src/treetensornetworks/treetensornetwork.jl @@ -119,75 +119,7 @@ end # set_ortho_region: low-level update of the ortho_region metadata only, # without any gauge transformations. To move the orthogonality center use orthogonalize. function set_ortho_region(tn::TTN, ortho_region) - return ttn(tn.tensornetwork; ortho_region) -end - -""" - ttn(args...; ortho_region=nothing) -> TreeTensorNetwork - -Construct a `TreeTensorNetwork` (TTN) using the same interface as [`ITensorNetwork`](@ref). -All positional and keyword arguments are forwarded to the `ITensorNetwork` constructor. - -If `ortho_region` is not specified, no particular gauge is assumed. -Call [`orthogonalize`](@ref) to impose a gauge. - -# Example - -```jldoctest -julia> using NamedGraphs.NamedGraphGenerators: named_comb_tree - -julia> g = named_comb_tree((2, 2)); - -julia> s = siteinds("S=1/2", g); - -julia> psi = ttn(v -> "Up", s); - -``` - -See also: [`mps`](@ref), [`TreeTensorNetwork`](@ref). -""" -function ttn(args...; ortho_region = nothing) - tn = ITensorNetwork(args...) - if isnothing(ortho_region) - ortho_region = vertices(tn) - end - return _TreeTensorNetwork(tn, ortho_region) -end - -""" - mps(args...; ortho_region=nothing) -> TreeTensorNetwork - -Construct a matrix product state (MPS) as a `TreeTensorNetwork` on a 1D path graph. -The interface is identical to [`ttn`](@ref) but is intended for 1D (chain) topologies. - -See also: [`ttn`](@ref). -""" -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 - -""" - mps(f, is::Vector{<:Index}; kwargs...) -> TreeTensorNetwork - -Construct a matrix product state (MPS) from a function `f` and a flat vector of site -indices `is`. The indices are arranged on a 1D path graph automatically. - -# Example - -```jldoctest -julia> s = siteinds("S=1/2", 6); - -julia> psi = mps(v -> "Up", s); - -``` -""" -function mps(f, is::Vector{<:Index}; kwargs...) - return mps(f, path_indsnetwork(is); kwargs...) + return TreeTensorNetwork(tn.tensornetwork; ortho_region) end """ @@ -227,7 +159,15 @@ function ttn( @assert hasinds(a, is[v]) end @assert ortho_region ⊆ vertices(is) - tn = ITensorNetwork(is) + is = insert_linkinds(is) + ts = Dict{vertextype(is), ITensor}() + for v in vertices(is) + site_inds = get(is, v, Index[]) + edges_v = [edgetype(is)(v, nv) for nv in neighbors(is, v)] + link_inds = reduce(vcat, (is[e] for e in edges_v); init = Index[]) + ts[v] = ITensor(site_inds..., link_inds...) + end + tn = ITensorNetwork(ts) ortho_center = first(ortho_region) for e in post_order_dfs_edges(tn, ortho_center) left_inds = setdiff(inds(a), inds(tn[dst(e)])) @@ -237,6 +177,6 @@ function ttn( a = a_r end tn[ortho_center] = a - ttn_a = ttn(tn) + ttn_a = TreeTensorNetwork(tn) return orthogonalize(ttn_a, ortho_center) end diff --git a/test/Project.toml b/test/Project.toml index c861b038..6370637f 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -46,7 +46,7 @@ Glob = "1.3.1" Graphs = "1.12" GraphsFlows = "0.1.1" ITensorMPS = "0.3.6, 0.4" -ITensorNetworks = "0.21" +ITensorNetworks = "0.22" ITensorPkgSkeleton = "0.3.42" ITensors = "0.7, 0.8, 0.9" KrylovKit = "0.8, 0.9, 0.10" diff --git a/test/solvers/test_applyexp.jl b/test/solvers/test_applyexp.jl index 81f0e0e4..6e0448ec 100644 --- a/test/solvers/test_applyexp.jl +++ b/test/solvers/test_applyexp.jl @@ -1,12 +1,13 @@ using Graphs: add_edge!, add_vertex!, vertices -using ITensorNetworks: - ITensorNetworks, applyexp, dmrg, maxlinkdim, siteinds, time_evolve, ttn +using ITensorNetworks: ITensorNetworks, TreeTensorNetwork, applyexp, dmrg, maxlinkdim, + siteinds, time_evolve, ttn using ITensors using ITensors.Ops: OpSum using NamedGraphs.NamedGraphGenerators: named_path_graph using NamedGraphs: NamedGraph using TensorOperations: TensorOperations using Test: @test, @testset #For contraction order finding +include(joinpath(@__DIR__, "..", "utils.jl")) function chain_plus_ancilla(; nchain) g = NamedGraph() @@ -45,7 +46,7 @@ end for (j, v) in enumerate(vertices(sites)) state[v] = iseven(j) ? "Up" : "Dn" end - psi0 = ttn(state, sites) + psi0 = TreeTensorNetwork(tensornetworkstate(state, sites)) cutoff = 1.0e-10 maxdim = 100 @@ -96,7 +97,7 @@ end for (j, v) in enumerate(vertices(sites)) state[v] = iseven(j) ? "Up" : "Dn" end - psi0 = ttn(state, sites) + psi0 = TreeTensorNetwork(tensornetworkstate(state, sites)) nsites = 2 factorize_kwargs = (; cutoff = 1.0e-8, maxdim = 100) diff --git a/test/solvers/test_eigsolve.jl b/test/solvers/test_eigsolve.jl index 6be2e4bd..8b3edc52 100644 --- a/test/solvers/test_eigsolve.jl +++ b/test/solvers/test_eigsolve.jl @@ -1,11 +1,12 @@ using Graphs: dst, edges, src, vertices -using ITensorNetworks: SweepIterator, dmrg, siteinds, ttn +using ITensorNetworks: SweepIterator, TreeTensorNetwork, dmrg, siteinds, ttn using ITensors using ITensors.Ops: OpSum using Suppressor: @capture_out using TensorOperations: TensorOperations using Test: @test, @testset +include(joinpath(@__DIR__, "..", "utils.jl")) include("utilities/simple_ed_methods.jl") include("utilities/tree_graphs.jl") @@ -31,7 +32,7 @@ include("utilities/tree_graphs.jl") for (j, v) in enumerate(vertices(sites)) state[v] = iseven(j) ? "Up" : "Dn" end - psi0 = ttn(state, sites) + psi0 = TreeTensorNetwork(tensornetworkstate(state, sites)) (outputlevel >= 1) && println("Computing exact ground state") Ex, psix = ed_ground_state(H, psi0) diff --git a/test/test_additensornetworks.jl b/test/test_additensornetworks.jl index b9676862..d812e9e3 100644 --- a/test/test_additensornetworks.jl +++ b/test/test_additensornetworks.jl @@ -1,5 +1,6 @@ using Graphs: rem_edge!, vertices -using ITensorNetworks: ITensorNetwork, inner_network, orthogonalize, siteinds, truncate, ttn +using ITensorNetworks: + ITensorNetwork, TreeTensorNetwork, inner_network, orthogonalize, siteinds, truncate using ITensors: ITensors, apply, inner, op, scalar using LinearAlgebra: norm_sqr using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_grid @@ -11,8 +12,8 @@ include("utils.jl") @testset "add_itensornetworks" begin g = named_grid((2, 2)) s = siteinds("S=1/2", g) - ψ1 = ITensorNetwork(v -> "↑", s) - ψ2 = ITensorNetwork(v -> "↓", s) + ψ1 = tensornetworkstate(v -> "↑", s) + ψ2 = tensornetworkstate(v -> "↓", s) ψ_GHZ = ψ1 + ψ2 @@ -71,13 +72,13 @@ end for (j, v) in enumerate(verts) state1[v] = isodd(j) ? "Up" : "Dn" end - ψ1 = ttn(state1, sites) + ψ1 = TreeTensorNetwork(tensornetworkstate(state1, sites)) state2 = Dict{Tuple, String}() for (j, v) in enumerate(vertices(g)) state2[v] = isodd(j) ? "Dn" : "Up" end - ψ2 = ttn(state2, sites) + ψ2 = TreeTensorNetwork(tensornetworkstate(state2, sites)) ϕ = ψ1 + ψ2 diff --git a/test/test_expect.jl b/test/test_expect.jl index 73a0891b..1b2ca119 100644 --- a/test/test_expect.jl +++ b/test/test_expect.jl @@ -43,7 +43,7 @@ include("utils.jl") g = named_grid((L, L)) s = siteinds("S=1/2", g; conserve_qns = true) - ψ = ITensorNetwork(v -> isodd(sum(v)) ? "↑" : "↓", s) + ψ = tensornetworkstate(v -> isodd(sum(v)) ? "↑" : "↓", s) sz_bp = expect(ψ, "Sz"; alg = "bp", cache_update_kwargs = (; maxiter = 20)) sz_exact = expect(ψ, "Sz"; alg = "exact") diff --git a/test/test_itensornetwork.jl b/test/test_itensornetwork.jl index 7f1b6926..891b4176 100644 --- a/test/test_itensornetwork.jl +++ b/test/test_itensornetwork.jl @@ -28,7 +28,8 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @test nv(s) == 4 @test ne(s) == 3 @test neighbors(s, (2,)) == [(1,), (3,)] - tn = ITensorNetwork(s; link_space = 2) + rng = StableRNG(1234) + tn = random_tensornetwork(rng, s; link_space = 2) @test nv(tn) == 4 @test ne(tn) == 3 @test tn isa ITensorNetwork @@ -54,10 +55,6 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @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 @@ -69,7 +66,7 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @test inner_res isa Float64 # test that by default vertices are linked by bond-dimension 1 index - tn = ITensorNetwork(s) + tn = random_tensornetwork(rng, s) @test isone(ITensors.dim(commonind(tn[(1,)], tn[(2,)]))) end @@ -92,7 +89,7 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) dims = (2, 2) g = named_grid(dims) s = siteinds("S=1/2", g) - ψ = ITensorNetwork(v -> "↑", s) + ψ = tensornetworkstate(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")) @@ -103,7 +100,7 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) dims = (2, 2) g = named_grid(dims) s = siteinds("S=1/2", g) - ψ = ITensorNetwork(v -> "↑", s) + ψ = tensornetworkstate(v -> "↑", s) rem_vertex!(ψ, (1, 2)) tn = norm_sqr_network(ψ) @test !has_vertex(tn, ((1, 2), "bra")) @@ -126,9 +123,7 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) ) rng = StableRNG(1234) - ψ = ITensorNetwork(g; kwargs...) do v - return inds -> itensor(randn(rng, elt, dim.(inds)...), inds) - end + ψ = random_tensornetwork(rng, elt, g; kwargs...) @test eltype(ψ[first(vertices(ψ))]) == elt ψc = conj(ψ) @@ -141,89 +136,11 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @test ψd[v] == dag(ψ[v]) end - rng = StableRNG(1234) - ψ = ITensorNetwork(g; kwargs...) do v - return inds -> itensor(randn(rng, dim.(inds)...), inds) - 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 - 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 @testset "random_tensornetwork with custom distributions" begin distribution = Uniform(-1.0, 1.0) rng = StableRNG(1234) @@ -256,7 +173,8 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) end @testset "dijkstra_shortest_paths" begin - tn = ITensorNetwork(named_grid(4); link_space = 2) + rng = StableRNG(1234) + tn = random_tensornetwork(rng, 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]) @@ -264,7 +182,8 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) end @testset "mincut" begin - tn = ITensorNetwork(named_grid(4); link_space = 3) + rng = StableRNG(1234) + tn = random_tensornetwork(rng, named_grid(4); link_space = 3) w = weights(tn) @test w isa Dictionary{Tuple{Int, Int}, Float64} @test length(w) ≈ ne(tn) @@ -286,7 +205,8 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) dims = (2, 2) g = named_grid(dims) s = siteinds("S=1/2", g) - ψ = ITensorNetwork(s; link_space = 2) + rng = StableRNG(1234) + ψ = random_tensornetwork(rng, s; link_space = 2) e = (1, 1) => (2, 1) uie = setdiff(inds(ψ[1, 1]), inds(ψ[2, 1])) @@ -321,14 +241,14 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) g = named_grid(dims) s = siteinds("S=1/2", g) state_map(v::Tuple) = iseven(sum(isodd.(v))) ? "↑" : "↓" - ψ = ITensorNetwork(state_map, s) + ψ = tensornetworkstate(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) + ϕ = tensornetworkstate(elt, state_map, s) t = ϕ[2, 2] si = only(siteinds(ϕ, (2, 2))) bi = map(e -> only(linkinds(ϕ, e)), incident_edges(ϕ, (2, 2))) diff --git a/test/test_tebd.jl b/test/test_tebd.jl index 8ff87991..3e5b1564 100644 --- a/test/test_tebd.jl +++ b/test/test_tebd.jl @@ -27,7 +27,7 @@ ITensors.disable_warn_order() β = 2.0 Δβ = 0.2 - ψ_init = ITensorNetwork(v -> "↑", s) + ψ_init = tensornetworkstate(v -> "↑", s) #E0 = expect(ℋ, ψ_init) ψ = tebd( group_terms(ℋ, g), diff --git a/test/test_ttn_expect.jl b/test/test_ttn_expect.jl index e9fabd7d..fbd74f49 100644 --- a/test/test_ttn_expect.jl +++ b/test/test_ttn_expect.jl @@ -1,9 +1,10 @@ using Graphs: vertices -using ITensorNetworks: expect, siteinds, ttn +using ITensorNetworks: TreeTensorNetwork, expect, siteinds using LinearAlgebra: norm using NamedGraphs.NamedGraphGenerators: named_comb_tree using StableRNGs: StableRNG using Test: @test, @testset +include("utils.jl") @testset "TTN expect" begin tooth_lengths = fill(2, 2) c = named_comb_tree(tooth_lengths) @@ -15,7 +16,7 @@ using Test: @test, @testset magnetization[v] = isodd(i) ? 0.5 : -0.5 end states = v -> d[v] - state = ttn(states, s) + state = TreeTensorNetwork(tensornetworkstate(states, s)) res = expect("Sz", state) @test all([isapprox(res[v], magnetization[v]; atol = 1.0e-8) for v in vertices(s)]) end diff --git a/test/test_ttn_position.jl b/test/test_ttn_position.jl index 5be35608..8c9314a2 100644 --- a/test/test_ttn_position.jl +++ b/test/test_ttn_position.jl @@ -1,10 +1,11 @@ using Dictionaries: Dictionary, Indices using Graphs: vertices -using ITensorNetworks: ITensorNetwork, ProjTTN, environments, position, siteinds, ttn +using ITensorNetworks: + ITensorNetwork, ProjTTN, TreeTensorNetwork, environments, position, siteinds, ttn using ITensors.NDTensors: with_auto_fermion using ITensors: ITensor using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_path_graph -using NamedGraphs: NamedEdge +using NamedGraphs: NamedEdge, NamedGraph using Test: @test, @testset include("utils.jl") using .ModelHamiltonians: ModelHamiltonians @@ -31,7 +32,7 @@ using .ModelHamiltonians: ModelHamiltonians d[v] = isodd(i) ? "Up" : "Dn" end states = v -> d[v] - psi = ttn(states, s) + psi = TreeTensorNetwork(tensornetworkstate(states, s)) # actual test, verifies that position is out of place vs = collect(vertices(s)) @@ -46,8 +47,9 @@ using .ModelHamiltonians: ModelHamiltonians end @testset "ProjTTN construction regression test" begin pos = Indices{Tuple{String, Int}}() - g = named_path_graph(2) - operator = ttn(ITensorNetwork{Any}(g)) + g = NamedGraph{Any}(named_path_graph(2)) + tensors = Dict{Any, ITensor}(v => ITensor() for v in vertices(g)) + operator = TreeTensorNetwork(ITensorNetwork{Any}(tensors, g)) environments = Dictionary{NamedEdge{Any}, ITensor}() @test ProjTTN(pos, operator, environments) isa ProjTTN{Any, Indices{Any}} end diff --git a/test/test_ttns.jl b/test/test_ttns.jl index 1c045db9..e55be719 100644 --- a/test/test_ttns.jl +++ b/test/test_ttns.jl @@ -8,6 +8,7 @@ using NamedGraphs.NamedGraphGenerators: named_comb_tree using Random: shuffle using StableRNGs: StableRNG using Test: @test, @testset +include("utils.jl") @testset "TTN Basics" begin # random comb tree rng = StableRNG(1234) @@ -37,8 +38,9 @@ using Test: @test, @testset g = named_comb_tree((3, 2)) sites = siteinds("S=1/2", g) - psi = ttn(sites) # zero-initialised - psi = ttn(v -> "Up", sites) # product state + rng = StableRNG(1234) + psi = TreeTensorNetwork(random_tensornetwork(rng, sites)) # random + psi = TreeTensorNetwork(tensornetworkstate(v -> "Up", sites)) # product state itn = ITensorNetwork(psi) # TTN → ITensorNetwork @test vertex_data(itn) == vertex_data(psi.tensornetwork) @@ -50,8 +52,9 @@ using Test: @test, @testset g = named_comb_tree((3, 2)) sites = siteinds("S=1/2", g) - psi = ttn(sites) # zero-initialised - psi = ttn(v -> "Up", sites) # product state + rng = StableRNG(1234) + psi = TreeTensorNetwork(random_tensornetwork(rng, sites)) # random + psi = TreeTensorNetwork(tensornetworkstate(v -> "Up", sites)) # product state v1 = collect(vertices(psi))[1] v2 = collect(vertices(psi))[2] diff --git a/test/utils.jl b/test/utils.jl index 69a54020..5c0d6cc5 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -3,14 +3,17 @@ # `test_*.jl`). Each test file that needs these does `include("utils.jl")` # inside its gensym module. -using DataGraphs: IsUnderlyingGraph, vertex_data +using DataGraphs: IsUnderlyingGraph, underlying_graph, vertex_data +using Dictionaries: AbstractDictionary, Indices using Distributions: Distribution -using Graphs: edges, vertices -using ITensorNetworks: ITensorNetworks, ITensorNetwork, IndsNetwork +using Graphs: AbstractGraph, edges, neighbors, vertices +using ITensorNetworks: ITensorNetworks, ITensorNetwork, IndsNetwork, insert_linkinds using ITensors.NDTensors: dim -using ITensors: ITensor, Index, itensor +using ITensors.Ops: Op +using ITensors: ITensors, ITensor, Index, contract, itensor, onehot, plev using LinearAlgebra: normalize -using NamedGraphs.GraphsExtensions: incident_edges +using NamedGraphs.GraphsExtensions: edgetype, incident_edges, vertextype +using NamedGraphs: NamedGraph using Random: Random, AbstractRNG using SimpleTraits: SimpleTraits, @traitfn @@ -24,13 +27,14 @@ function _random_tensornetwork(s::IndsNetwork, build_tensor; link_space = 1) l = Dict(e => Index(link_space, "Link") for e in edges(s)) l = merge(l, Dict(reverse(e) => l[e] for e in edges(s))) vd = vertex_data(s) - ts = Dict{eltype(vertices(s)), ITensor}() - for v in vertices(s) + g = NamedGraph(underlying_graph(s)) + ts = Dict{vertextype(g), ITensor}() + for v in vertices(g) site_inds = isassigned(vd, v) ? vd[v] : Index[] is = (site_inds..., (l[e] for e in incident_edges(s, v))...) ts[v] = build_tensor(is) end - return ITensorNetwork(ts) + return ITensorNetwork(ts, g) end # Build an ITensor network on a graph specified by the inds network `s`. @@ -56,6 +60,98 @@ function random_tensornetwork(s::IndsNetwork; kwargs...) return random_tensornetwork(Random.default_rng(), s; kwargs...) end +# +# State / default tensornetwork builders. Replace deleted +# `ITensorNetwork(state, ::IndsNetwork)` / `ITensorNetwork(::Function, ::IndsNetwork)` +# / `ITensorNetwork(::IndsNetwork)` / `ITensorNetwork(::AbstractGraph)` ctor surface. +# + +# Convert a state spec into a callable mapping vertex -> state-spec-for-that-vertex. +to_callable(value::Type) = value +to_callable(value::Function) = value +function to_callable(value::AbstractDict) + return Base.Fix1(getindex, value) ∘ keytype(value) +end +function to_callable(value::AbstractDictionary) + return Base.Fix1(getindex, value) ∘ keytype(value) +end +function to_callable(value::AbstractArray{<:Any, N}) where {N} + return Base.Fix1(getindex, value) ∘ CartesianIndex +end +to_callable(value) = Returns(value) + +# Build the actual ITensor at a vertex from a state spec and the (site, link) inds. +generic_state(f, inds::Vector) = f(inds) +generic_state(a::AbstractArray, inds::Vector) = itensor(a, inds) +function generic_state(f, inds::NamedTuple) + return generic_state(f, reduce(vcat, inds.linkinds; init = inds.siteinds)) +end +function generic_state(x::Op, inds::NamedTuple) + 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 = [ITensors.op(x.which_op, i)] + else + site_tensors = ITensor[] + 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) + site_tensors = [ITensors.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 + +# Lowest-level: function-callback form. `itensor_constructor(v)` returns a +# state spec for vertex `v`, which is then expanded into an actual `ITensor` +# via `generic_state`. +function tensornetworkstate(itensor_constructor::Function, s::IndsNetwork; kwargs...) + s = insert_linkinds(s; kwargs...) + g = NamedGraph(underlying_graph(s)) + ts = Dict{vertextype(g), ITensor}() + for v in vertices(g) + site_inds = get(s, v, Index[]) + edges = [edgetype(s)(v, nv) for nv in neighbors(s, v)] + link_inds = map(e -> s[e], Indices(edges)) + ts[v] = generic_state( + itensor_constructor(v), (; siteinds = site_inds, linkinds = link_inds) + ) + end + return ITensorNetwork(ts, g) +end + +# Convenience: convert a value (string, dict, function, etc.) to a per-vertex +# callable, then build. +function tensornetworkstate(value, s::IndsNetwork; kwargs...) + return tensornetworkstate(to_callable(value), s; kwargs...) +end + +# Eltype-converting variant: build, then convert each tensor to `elt`. +function tensornetworkstate(elt::Type, value, s::IndsNetwork; kwargs...) + tn = tensornetworkstate(to_callable(value), s; kwargs...) + for v in vertices(tn) + tn[v] = ITensors.convert_eltype(elt, tn[v]) + end + return tn +end + +# Graph-input forms: build the IndsNetwork first (no site inds by default), +# then forward. +@traitfn function tensornetworkstate(value::Any, g::::IsUnderlyingGraph; kwargs...) + return tensornetworkstate(value, IndsNetwork(g); kwargs...) +end +@traitfn function tensornetworkstate( + elt::Type, value::Any, g::::IsUnderlyingGraph; kwargs... + ) + return tensornetworkstate(elt, value, IndsNetwork(g); kwargs...) +end + @traitfn function random_tensornetwork( rng::AbstractRNG, eltype::Type, g::::IsUnderlyingGraph; kwargs... ) From 9ef3eb60b9395e8c3593712c76d88026fcb5fa05 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 7 May 2026 20:07:46 -0400 Subject: [PATCH 2/6] [BREAKING] Drop ttn/mps name aliases and simplify test/utils.jl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the IndsNetwork/graph ctor removal in the same branch. Drop the `ttn` / `mps` short-name surface entirely (rather than narrowing it): the surviving constructors keep the canonical `TreeTensorNetwork` name. This covers `ttn(::OpSum, ::IndsNetwork)` (and its `eltype`-leading variant) and `ttn(::ITensor, ::IndsNetwork)`. `mpo` continues to dispatch through to `TreeTensorNetwork(::OpSum, ::IndsNetwork)`. Docs: drop `ttn` / `mps` from `using ITensorNetworks` in `docs/make.jl` and the `mps`-specific section from `tree_tensor_networks.md`; rewrite the constructor examples in `itensor_networks.md` and `tree_tensor_networks.md` to build link inds explicitly with `random_itensor` so they don't fall back to `EmptyNumber` placeholders that fail the downstream `add` / `qr` / `directsum` examples; rename `ttn` callsites to `TreeTensorNetwork` in `solvers.md` and `experimental_methods.md`. Fixes the docs build failure on PR #356. `src/abstractitensornetwork.jl`: `_siteinds` now `collect`s the inds before iterating, so the return type is a `Vector{Index}` rather than a `Tuple`. The `IndsNetwork` `vertex_data` setter expects a vector, and the previous behaviour produced a `Tuple` whenever a vertex's tensor had no shared neighbour inds. Also rename `truncate(ttn) → truncate(tn)` in a docstring. `test/utils.jl`: substantial simplification (~261 → ~75 effective lines). Drop `random_mps`, `random_ttn`, `Op`-handling, the `generic_state` machinery, and the `@traitfn` indirection. Surviving surface: `random_tensornetwork` (graph or `IndsNetwork` input, eltype + distribution variants), `productstate` (renamed from `tensornetworkstate`, since it only builds product states; routed through `insert_linkinds` so the default `link_space` follows `trivial_space` and stays QN-aware), and the `ModelHamiltonians` submodule. Test callsites updated: `ttn(state, s) → TreeTensorNetwork(productstate(state, s))`, `ttn(os, s) → TreeTensorNetwork(os, s)`, `ttn(A, is; cutoff) → TreeTensorNetwork(A, is; cutoff)`, and `tensornetworkstate → productstate`. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/make.jl | 2 +- docs/src/experimental_methods.md | 2 +- docs/src/itensor_networks.md | 34 ++- docs/src/solvers.md | 6 +- docs/src/tree_tensor_networks.md | 35 ++- src/abstractitensornetwork.jl | 4 +- src/itensornetwork.jl | 2 +- src/solvers/eigsolve.jl | 2 +- .../opsum_to_ttn/opsum_to_ttn.jl | 14 +- src/treetensornetworks/treetensornetwork.jl | 51 ++-- test/solvers/test_applyexp.jl | 12 +- test/solvers/test_eigsolve.jl | 6 +- test/test_additensornetworks.jl | 8 +- test/test_expect.jl | 2 +- test/test_inner.jl | 6 +- test/test_itensornetwork.jl | 10 +- test/test_opsum_to_ttn.jl | 8 +- test/test_opsum_to_ttn_mpo_cross_check.jl | 16 +- test/test_tebd.jl | 2 +- test/test_ttn_expect.jl | 2 +- test/test_ttn_position.jl | 6 +- test/test_ttno.jl | 4 +- test/test_ttns.jl | 8 +- test/utils.jl | 261 ++++++------------ 24 files changed, 208 insertions(+), 295 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 18cf0162..0e4e7e01 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -14,7 +14,7 @@ DocMeta.setdocmeta!( using Graphs: dst, edges, src, vertices using ITensorNetworks using ITensorNetworks: - TreeTensorNetwork, expect, loginner, mps, orthogonalize, siteinds, truncate, ttn + TreeTensorNetwork, expect, loginner, orthogonalize, siteinds, truncate using ITensors: inner using LinearAlgebra: norm, normalize using OMEinsumContractionOrders diff --git a/docs/src/experimental_methods.md b/docs/src/experimental_methods.md index 14d28373..1c1895fc 100644 --- a/docs/src/experimental_methods.md +++ b/docs/src/experimental_methods.md @@ -50,7 +50,7 @@ Methods which still need to be discussed, modified, or deprecated. * From an `OpSum`, using `opsum_to_ttn.jl` code: ```julia - ttn(os::OpSum, sites::IndsNetwork; kws...) + TreeTensorNetwork(os::OpSum, sites::IndsNetwork; kws...) ``` * From `OpSum`, assuming path graph (`opsum_to_ttn.jl`): diff --git a/docs/src/itensor_networks.md b/docs/src/itensor_networks.md index 1961f6e6..d14dc7fc 100644 --- a/docs/src/itensor_networks.md +++ b/docs/src/itensor_networks.md @@ -32,22 +32,36 @@ tn = ITensorNetwork([A, B, C]) # integer vertices 1, 2, 3 tn = ITensorNetwork(Dict("A" => A, "B" => B, "C" => C)) # named vertices via a Dict ``` -If you want to control edges directly — for example to build an empty network on a +If you want to control edges directly — for example to build a structured network on a prescribed lattice and fill in tensors later — pass a `NamedGraph` along with a -collection of `ITensor`s indexed by vertex: +collection of `ITensor`s indexed by vertex. To create a tensor network with shared link +indices on each edge, build the indices once per edge and reuse them at both endpoints: ```@example main -g = named_grid((3, 3)) -s = siteinds("S=1/2", g) # one spin-½ Index per vertex +using ITensors: random_itensor +using NamedGraphs: NamedGraph +using NamedGraphs.GraphsExtensions: edgetype, incident_edges + +g = NamedGraph(named_grid((3, 3))) +s = siteinds("S=1/2", g) # one spin-½ site Index per vertex + +# One shared link Index per edge; bond dimension χ +χ = 2 +links = Dict(e => Index(χ, "Link") for e in edges(g)) + +# Per-vertex tensor: the site Index plus the link Index of every incident edge +tensors = Dict(map(collect(vertices(g))) do v + site_v = s[v] + link_v = [haskey(links, e) ? links[e] : links[reverse(e)] for e in incident_edges(g, v)] + return v => random_itensor(site_v..., link_v...) +end) -# Build site tensors on the 3×3 lattice with one (placeholder) site index each -tensors = Dict(v => ITensor(s[v]...) for v in vertices(g)) ψ = ITensorNetwork(tensors, g) ``` Higher-level construction routines (random networks, product states, OpSum-derived -TTNs, etc.) are provided by sibling functions like `ttn(opsum, sites)` and the test-only -helpers in `test/utils.jl`. +TTNs, etc.) are provided by sibling functions like `TreeTensorNetwork(opsum, sites)` +and the test-only helpers in `test/utils.jl`. ```@docs; canonical=false ITensorNetworks.ITensorNetwork @@ -72,7 +86,7 @@ linkinds(ψ) # IndsNetwork of bond (virtual) indices Two networks with the same graph and site indices can be added. The result represents the tensor network `ψ₁ + ψ₂` and has bond dimension equal to the **sum** of the two input bond dimensions. Individual bonds of the result can be recompressed with `truncate(tn, edge)`. -For `TreeTensorNetwork`, the no-argument form `truncate(ttn; kwargs...)` sweeps and +For `TreeTensorNetwork`, the no-argument form `truncate(tn; kwargs...)` sweeps and recompresses all bonds at once. ```@example main @@ -96,7 +110,7 @@ edge = (1, 2) => (1, 3) ``` Truncation parameters (`cutoff`, `maxdim`, `mindim`, …) are forwarded to `ITensors.svd`. -For a `TreeTensorNetwork`, the sweep-based `truncate(ttn; kwargs...)` is usually more +For a `TreeTensorNetwork`, the sweep-based `truncate(tn; kwargs...)` is usually more convenient because it recompresses the entire network at once with controlled errors; see the [Tree Tensor Networks](@ref) page. diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 6a9b7218..d8bf4976 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -18,7 +18,7 @@ variational sweep algorithm. ```@example main using Graphs: vertices using ITensorNetworks: - ITensorNetwork, TreeTensorNetwork, dmrg, dst, edges, normalize, siteinds, src, ttn + ITensorNetwork, TreeTensorNetwork, dmrg, dst, edges, normalize, siteinds, src using ITensors: Index, OpSum, random_itensor using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs.NamedGraphGenerators: named_comb_tree @@ -30,7 +30,7 @@ function random_state(g, s; link_space) v => random_itensor(only(s[v]), (l[e] for e in incident_edges(g, v))...) for v in vertices(g) ) - return ITensorNetwork(ts) + return ITensorNetwork(ts, g) end # Build a Heisenberg Hamiltonian on a comb tree @@ -42,7 +42,7 @@ H = let h = OpSum() h += 0.5, "S-", src(e), "S+", dst(e) h += "Sz", src(e), "Sz", dst(e) end - ttn(h, s) + TreeTensorNetwork(h, s) end # Random initial state (normalise first!) diff --git a/docs/src/tree_tensor_networks.md b/docs/src/tree_tensor_networks.md index b42ec9b2..ff740ffb 100644 --- a/docs/src/tree_tensor_networks.md +++ b/docs/src/tree_tensor_networks.md @@ -21,7 +21,7 @@ A common way to obtain a Hamiltonian-shaped TTN is to convert an `OpSum` over an `IndsNetwork` of site indices. ```@docs; canonical=false -ITensorNetworks.ttn(::ITensors.Ops.OpSum, ::ITensorNetworks.IndsNetwork) +ITensorNetworks.TreeTensorNetwork(::ITensors.Ops.OpSum, ::ITensorNetworks.IndsNetwork) ``` ### From an existing `ITensorNetwork` @@ -32,21 +32,28 @@ orthogonality region. Use the `TreeTensorNetwork` constructor to convert a plain gauge metadata when you need a plain network again. ```@example main -using Graphs: vertices +using Graphs: edges, vertices using ITensorNetworks: ITensorNetwork, TreeTensorNetwork, ortho_region, orthogonalize, - siteinds, ttn -using ITensors: ITensors, ITensor + siteinds +using ITensors: ITensors, Index, random_itensor using LinearAlgebra: norm using NamedGraphs: NamedGraph +using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs.NamedGraphGenerators: named_comb_tree # Comb-tree TTN (a popular tree topology for 2D-like systems) -g = named_comb_tree((3, 2)) +g = NamedGraph(named_comb_tree((3, 2))) sites = siteinds("S=1/2", g) -# Build an `ITensorNetwork` from explicit per-vertex tensors and wrap as a TTN -tensors = Dict(v => ITensor(sites[v]...) for v in vertices(g)) -itn = ITensorNetwork(tensors, NamedGraph(g)) +# Build a structured `ITensorNetwork` with shared link indices on each edge +χ = 2 +links = Dict(e => Index(χ, "Link") for e in edges(g)) +tensors = Dict(map(collect(vertices(g))) do v + site_v = sites[v] + link_v = [haskey(links, e) ? links[e] : links[reverse(e)] for e in incident_edges(g, v)] + return v => random_itensor(site_v..., link_v...) +end) +itn = ITensorNetwork(tensors, g) psi = TreeTensorNetwork(itn) ``` @@ -68,14 +75,16 @@ tree edges. Truncation parameters (e.g. `cutoff`, `maxdim`) are forwarded to the factorisation step. ```@example main -g = named_comb_tree((3, 1)) -sites = siteinds("S=1/2", g) -A = ITensors.random_itensor(only(sites[(1, 1)]), only(sites[(2, 1)]), only(sites[(3, 1)])) -ttn_A = ttn(A, sites) +g_small = named_comb_tree((3, 1)) +sites_small = siteinds("S=1/2", g_small) +A = ITensors.random_itensor( + only(sites_small[(1, 1)]), only(sites_small[(2, 1)]), only(sites_small[(3, 1)]) +) +ttn_A = TreeTensorNetwork(A, sites_small) ``` ```@docs; canonical=false -ITensorNetworks.ttn(::ITensors.ITensor, ::ITensorNetworks.IndsNetwork) +ITensorNetworks.TreeTensorNetwork(::ITensors.ITensor, ::ITensorNetworks.IndsNetwork) ``` ## Orthogonal Gauge diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index d5382d02..cbda61c1 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -274,7 +274,7 @@ end # function _siteinds(tn::AbstractITensorNetwork, vertex) - s = inds(tn[vertex]) + s = collect(inds(tn[vertex])) for v in neighbors(tn, vertex) s = setdiff(s, inds(tn[v])) end @@ -681,7 +681,7 @@ Truncation parameters are passed as keyword arguments and forwarded to `ITensors - `mindim`: Minimum number of singular values to keep. This operates on a single bond. For `TreeTensorNetwork`, the no-argument form -`truncate(ttn; kwargs...)` sweeps all bonds and is generally preferred for full +`truncate(tn; kwargs...)` sweeps all bonds and is generally preferred for full recompression after addition or subspace expansion. See also: `Base.truncate(::AbstractTreeTensorNetwork)`. diff --git a/src/itensornetwork.jl b/src/itensornetwork.jl index 00c7f6c6..e0f6ce38 100644 --- a/src/itensornetwork.jl +++ b/src/itensornetwork.jl @@ -38,7 +38,7 @@ julia> tn = ITensorNetwork([ITensor(i, j), ITensor(j, k)]); ``` -See also: `IndsNetwork`, [`ttn`](@ref ITensorNetworks.ttn), [`TreeTensorNetwork`](@ref ITensorNetworks.TreeTensorNetwork). +See also: `IndsNetwork`, [`TreeTensorNetwork`](@ref ITensorNetworks.TreeTensorNetwork). """ const _ITensorCollection = Union{ AbstractVector{<:ITensor}, diff --git a/src/solvers/eigsolve.jl b/src/solvers/eigsolve.jl index dd741c61..2679147d 100644 --- a/src/solvers/eigsolve.jl +++ b/src/solvers/eigsolve.jl @@ -70,7 +70,7 @@ DMRG-like sweep algorithm on a `TreeTensorNetwork`. # Arguments - `operator`: The operator to diagonalize, typically a `TreeTensorNetwork` representing a - Hamiltonian constructed from an `OpSum` (e.g. via `ttn(opsum, sites)`). + Hamiltonian constructed from an `OpSum` (e.g. via `TreeTensorNetwork(opsum, sites)`). - `init_state`: Initial guess for the eigenvector as a `TreeTensorNetwork`. - `nsweeps`: Number of sweeps over the network. - `nsites=1`: Number of sites optimized simultaneously per local update step (1 or 2). diff --git a/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl b/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl index ad00bba2..0b607b3a 100644 --- a/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl +++ b/src/treetensornetworks/opsum_to_ttn/opsum_to_ttn.jl @@ -636,12 +636,12 @@ function sortmergeterms(os::OpSum{C}) where {C} end """ - ttn(os::OpSum, sites::IndsNetwork{<:Index}; kwargs...) - ttn(eltype::Type{<:Number}, os::OpSum, sites::IndsNetwork{<:Index}; kwargs...) + TreeTensorNetwork(os::OpSum, sites::IndsNetwork{<:Index}; kwargs...) + TreeTensorNetwork(eltype::Type{<:Number}, os::OpSum, sites::IndsNetwork{<:Index}; kwargs...) Convert an OpSum object `os` to a TreeTensorNetwork, with indices given by `sites`. """ -function ttn( +function TreeTensorNetwork( os::OpSum, sites::IndsNetwork; root_vertex = GraphsExtensions.default_root_vertex(sites), @@ -658,16 +658,16 @@ function ttn( end function mpo(os::OpSum, external_inds::Vector; kwargs...) - return ttn(os, path_indsnetwork(external_inds); kwargs...) + return TreeTensorNetwork(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...) + return TreeTensorNetwork(os, 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...)) +function TreeTensorNetwork(eltype::Type{<:Number}, os, sites::IndsNetwork; kwargs...) + return NDTensors.convert_scalartype(eltype, TreeTensorNetwork(os, sites; kwargs...)) end # diff --git a/src/treetensornetworks/treetensornetwork.jl b/src/treetensornetworks/treetensornetwork.jl index 0f2843de..7dc7cb25 100644 --- a/src/treetensornetworks/treetensornetwork.jl +++ b/src/treetensornetworks/treetensornetwork.jl @@ -1,9 +1,10 @@ +using DataGraphs: underlying_graph using Dictionaries: Indices using Graphs: path_graph using ITensors: ITensor using LinearAlgebra: factorize, normalize using NamedGraphs.GraphsExtensions: GraphsExtensions, vertextype -using NamedGraphs: similar_graph +using NamedGraphs: NamedGraph, similar_graph """ TreeTensorNetwork{V} <: AbstractTreeTensorNetwork{V} @@ -14,10 +15,10 @@ center of the network. `TTN` is an alias for `TreeTensorNetwork`. -Use [`ttn`](@ref) or [`mps`](@ref) to construct instances, and [`orthogonalize`](@ref) to -bring the network into a canonical gauge. +Use the [`TreeTensorNetwork`](@ref) constructors to build instances, and +[`orthogonalize`](@ref) to bring the network into a canonical gauge. -See also: [`ITensorNetwork`](@ref), [`ttn`](@ref), [`mps`](@ref). +See also: [`ITensorNetwork`](@ref). """ struct TreeTensorNetwork{V} <: AbstractTreeTensorNetwork{V} tensornetwork::ITensorNetwork{V} @@ -52,21 +53,25 @@ Throws an error if the underlying graph of `tn` is not a tree. ```jldoctest julia> using NamedGraphs.NamedGraphGenerators: named_comb_tree +julia> using NamedGraphs: NamedGraph + julia> using Graphs: vertices +julia> using ITensors: ITensor + julia> g = named_comb_tree((2, 2)); julia> s = siteinds("S=1/2", g); -julia> itn = ITensorNetwork(s; link_space = 2); +julia> tensors = Dict(v => ITensor(s[v]...) for v in vertices(g)); -julia> root_vertex = first(vertices(itn)); +julia> itn = ITensorNetwork(tensors, NamedGraph(g)); -julia> ttn_state = TreeTensorNetwork(itn; ortho_region = [root_vertex]); +julia> ttn_state = TreeTensorNetwork(itn; ortho_region = [first(vertices(itn))]); ``` -See also: [`ttn`](@ref), [`ITensorNetwork`](@ref), [`orthogonalize`](@ref). +See also: [`ITensorNetwork`](@ref), [`orthogonalize`](@ref). """ function TreeTensorNetwork(tn::ITensorNetwork; ortho_region = vertices(tn)) return _TreeTensorNetwork(tn, ortho_region) @@ -88,7 +93,7 @@ end Convert a `TreeTensorNetwork` to a plain `ITensorNetwork`, discarding orthogonality metadata. The returned network shares the same underlying tensor data. -See also: [`TreeTensorNetwork`](@ref), [`ttn`](@ref). +See also: [`TreeTensorNetwork`](@ref). """ ITensorNetwork(tn::TTN) = copy(tn.tensornetwork) @@ -123,7 +128,7 @@ function set_ortho_region(tn::TTN, ortho_region) end """ - ttn(a::ITensor, is::IndsNetwork; ortho_region=..., kwargs...) -> TreeTensorNetwork + TreeTensorNetwork(a::ITensor, is::IndsNetwork; ortho_region=..., kwargs...) -> TreeTensorNetwork Decompose a dense `ITensor` `a` into a `TreeTensorNetwork` with the tree structure described by the `IndsNetwork` `is`. @@ -131,25 +136,8 @@ described by the `IndsNetwork` `is`. Successive QR/SVD factorizations are applied following a post-order DFS traversal from the root vertex, then the network is orthogonalized to `ortho_region` (defaults to the root). Extra `kwargs` (e.g. `cutoff`, `maxdim`) are forwarded to the factorization. - -# Example - -```jldoctest -julia> using NamedGraphs.NamedGraphGenerators: named_comb_tree - -julia> using ITensors: ITensors - -julia> g = named_comb_tree((3, 1)); - -julia> s = siteinds("S=1/2", g); - -julia> A = ITensors.random_itensor(only(s[(1, 1)]), only(s[(2, 1)]), only(s[(3, 1)])); - -julia> ttn_A = ttn(A, s); - -``` """ -function ttn( +function TreeTensorNetwork( a::ITensor, is::IndsNetwork; ortho_region = Indices([GraphsExtensions.default_root_vertex(is)]), @@ -160,14 +148,15 @@ function ttn( end @assert ortho_region ⊆ vertices(is) is = insert_linkinds(is) - ts = Dict{vertextype(is), ITensor}() - for v in vertices(is) + g = NamedGraph(underlying_graph(is)) + ts = Dict{vertextype(g), ITensor}() + for v in vertices(g) site_inds = get(is, v, Index[]) edges_v = [edgetype(is)(v, nv) for nv in neighbors(is, v)] link_inds = reduce(vcat, (is[e] for e in edges_v); init = Index[]) ts[v] = ITensor(site_inds..., link_inds...) end - tn = ITensorNetwork(ts) + tn = ITensorNetwork(ts, g) ortho_center = first(ortho_region) for e in post_order_dfs_edges(tn, ortho_center) left_inds = setdiff(inds(a), inds(tn[dst(e)])) diff --git a/test/solvers/test_applyexp.jl b/test/solvers/test_applyexp.jl index 6e0448ec..faa4e1db 100644 --- a/test/solvers/test_applyexp.jl +++ b/test/solvers/test_applyexp.jl @@ -1,6 +1,6 @@ using Graphs: add_edge!, add_vertex!, vertices -using ITensorNetworks: ITensorNetworks, TreeTensorNetwork, applyexp, dmrg, maxlinkdim, - siteinds, time_evolve, ttn +using ITensorNetworks: + ITensorNetworks, TreeTensorNetwork, applyexp, dmrg, maxlinkdim, siteinds, time_evolve using ITensors using ITensors.Ops: OpSum using NamedGraphs.NamedGraphGenerators: named_path_graph @@ -39,14 +39,14 @@ end h += 1 / 2, "S+", j, "S-", j + 1 h += 1 / 2, "S-", j, "S+", j + 1 end - H = ttn(h, sites) + H = TreeTensorNetwork(h, sites) # Make initial product state state = Dict{Int, String}() for (j, v) in enumerate(vertices(sites)) state[v] = iseven(j) ? "Up" : "Dn" end - psi0 = TreeTensorNetwork(tensornetworkstate(state, sites)) + psi0 = TreeTensorNetwork(productstate(state, sites)) cutoff = 1.0e-10 maxdim = 100 @@ -90,14 +90,14 @@ end h += 1 / 2, "S+", j, "S-", j + 1 h += 1 / 2, "S-", j, "S+", j + 1 end - H = ttn(h, sites) + H = TreeTensorNetwork(h, sites) # Initial product state state = Dict{Int, String}() for (j, v) in enumerate(vertices(sites)) state[v] = iseven(j) ? "Up" : "Dn" end - psi0 = TreeTensorNetwork(tensornetworkstate(state, sites)) + psi0 = TreeTensorNetwork(productstate(state, sites)) nsites = 2 factorize_kwargs = (; cutoff = 1.0e-8, maxdim = 100) diff --git a/test/solvers/test_eigsolve.jl b/test/solvers/test_eigsolve.jl index 8b3edc52..5805fdf1 100644 --- a/test/solvers/test_eigsolve.jl +++ b/test/solvers/test_eigsolve.jl @@ -1,5 +1,5 @@ using Graphs: dst, edges, src, vertices -using ITensorNetworks: SweepIterator, TreeTensorNetwork, dmrg, siteinds, ttn +using ITensorNetworks: SweepIterator, TreeTensorNetwork, dmrg, siteinds using ITensors using ITensors.Ops: OpSum using Suppressor: @capture_out @@ -25,14 +25,14 @@ include("utilities/tree_graphs.jl") h += 1 / 2, "S+", i, "S-", j h += 1 / 2, "S-", i, "S+", j end - H = ttn(h, sites) + H = TreeTensorNetwork(h, sites) # Make initial product state state = Dict{Tuple{Int, Int}, String}() for (j, v) in enumerate(vertices(sites)) state[v] = iseven(j) ? "Up" : "Dn" end - psi0 = TreeTensorNetwork(tensornetworkstate(state, sites)) + psi0 = TreeTensorNetwork(productstate(state, sites)) (outputlevel >= 1) && println("Computing exact ground state") Ex, psix = ed_ground_state(H, psi0) diff --git a/test/test_additensornetworks.jl b/test/test_additensornetworks.jl index d812e9e3..7503fd1f 100644 --- a/test/test_additensornetworks.jl +++ b/test/test_additensornetworks.jl @@ -12,8 +12,8 @@ include("utils.jl") @testset "add_itensornetworks" begin g = named_grid((2, 2)) s = siteinds("S=1/2", g) - ψ1 = tensornetworkstate(v -> "↑", s) - ψ2 = tensornetworkstate(v -> "↓", s) + ψ1 = productstate(v -> "↑", s) + ψ2 = productstate(v -> "↓", s) ψ_GHZ = ψ1 + ψ2 @@ -72,13 +72,13 @@ end for (j, v) in enumerate(verts) state1[v] = isodd(j) ? "Up" : "Dn" end - ψ1 = TreeTensorNetwork(tensornetworkstate(state1, sites)) + ψ1 = TreeTensorNetwork(productstate(state1, sites)) state2 = Dict{Tuple, String}() for (j, v) in enumerate(vertices(g)) state2[v] = isodd(j) ? "Dn" : "Up" end - ψ2 = TreeTensorNetwork(tensornetworkstate(state2, sites)) + ψ2 = TreeTensorNetwork(productstate(state2, sites)) ϕ = ψ1 + ψ2 diff --git a/test/test_expect.jl b/test/test_expect.jl index 1b2ca119..98040d98 100644 --- a/test/test_expect.jl +++ b/test/test_expect.jl @@ -43,7 +43,7 @@ include("utils.jl") g = named_grid((L, L)) s = siteinds("S=1/2", g; conserve_qns = true) - ψ = tensornetworkstate(v -> isodd(sum(v)) ? "↑" : "↓", s) + ψ = productstate(v -> isodd(sum(v)) ? "↑" : "↓", s) sz_bp = expect(ψ, "Sz"; alg = "bp", cache_update_kwargs = (; maxiter = 20)) sz_exact = expect(ψ, "Sz"; alg = "exact") diff --git a/test/test_inner.jl b/test/test_inner.jl index 0e0a796c..81df35a5 100644 --- a/test/test_inner.jl +++ b/test/test_inner.jl @@ -1,6 +1,6 @@ using Graphs: SimpleGraph, uniform_tree -using ITensorNetworks: ITensorNetwork, inner, inner_network, loginner, logscalar, scalar, - siteinds, ttn, underlying_graph +using ITensorNetworks: ITensorNetwork, TreeTensorNetwork, inner, inner_network, loginner, + logscalar, scalar, siteinds, underlying_graph using ITensors: dag using NamedGraphs: NamedGraph using SplitApplyCombine: group @@ -38,7 +38,7 @@ using .ModelHamiltonians: heisenberg @test xy_scalar ≈ xy_scalar_logbp #test contraction of three layers for expectation values - A = ITensorNetwork(ttn(heisenberg(g), s)) + A = ITensorNetwork(TreeTensorNetwork(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")) diff --git a/test/test_itensornetwork.jl b/test/test_itensornetwork.jl index 891b4176..868c9e5f 100644 --- a/test/test_itensornetwork.jl +++ b/test/test_itensornetwork.jl @@ -5,7 +5,7 @@ using Graphs: degree, dijkstra_shortest_paths, edges, grid, has_vertex, ne, neig using GraphsFlows: GraphsFlows using ITensorNetworks: ITensorNetworks, ITensorNetwork, IndsNetwork, contraction_sequence, inner_network, linkinds, norm_sqr, norm_sqr_network, orthogonalize, siteinds, - tree_orthogonalize, ttn, ⊗ + tree_orthogonalize, ⊗ using ITensors.NDTensors: NDTensors, dim using ITensors: ITensors, ITensor, Index, Op, commonind, commoninds, contract, dag, hasinds, inds, inner, itensor, onehot, order, prime, random_itensor, scalartype, sim @@ -89,7 +89,7 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) dims = (2, 2) g = named_grid(dims) s = siteinds("S=1/2", g) - ψ = tensornetworkstate(v -> "↑", s) + ψ = productstate(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")) @@ -100,7 +100,7 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) dims = (2, 2) g = named_grid(dims) s = siteinds("S=1/2", g) - ψ = tensornetworkstate(v -> "↑", s) + ψ = productstate(v -> "↑", s) rem_vertex!(ψ, (1, 2)) tn = norm_sqr_network(ψ) @test !has_vertex(tn, ((1, 2), "bra")) @@ -241,14 +241,14 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) g = named_grid(dims) s = siteinds("S=1/2", g) state_map(v::Tuple) = iseven(sum(isodd.(v))) ? "↑" : "↓" - ψ = tensornetworkstate(state_map, s) + ψ = productstate(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 - ϕ = tensornetworkstate(elt, state_map, s) + ϕ = productstate(elt, state_map, s) t = ϕ[2, 2] si = only(siteinds(ϕ, (2, 2))) bi = map(e -> only(linkinds(ϕ, e)), incident_edges(ϕ, (2, 2))) diff --git a/test/test_opsum_to_ttn.jl b/test/test_opsum_to_ttn.jl index f54076e8..ae8eb67a 100644 --- a/test/test_opsum_to_ttn.jl +++ b/test/test_opsum_to_ttn.jl @@ -1,5 +1,5 @@ using Graphs: vertices -using ITensorNetworks: ITensorNetworks, OpSum, siteinds, ttn +using ITensorNetworks: ITensorNetworks, OpSum, TreeTensorNetwork, siteinds using ITensors.NDTensors: with_auto_fermion using NamedGraphs.NamedGraphGenerators: named_grid using Test: @test, @testset @@ -15,9 +15,9 @@ using Test: @test, @testset 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) + H1 = TreeTensorNetwork(os1, s) + H2 = TreeTensorNetwork(os2, s) + H3 = TreeTensorNetwork(os1 + os2, s) @test H1 + H2 ≈ H3 rtol = 1.0e-6 end diff --git a/test/test_opsum_to_ttn_mpo_cross_check.jl b/test/test_opsum_to_ttn_mpo_cross_check.jl index a7b16143..a0509f44 100644 --- a/test/test_opsum_to_ttn_mpo_cross_check.jl +++ b/test/test_opsum_to_ttn_mpo_cross_check.jl @@ -3,7 +3,7 @@ using Dictionaries: Dictionary using Graphs: add_edge!, add_vertex!, rem_edge!, vertices using ITensorMPS: ITensorMPS using ITensorNetworks.ITensorsExtensions: replace_vertices -using ITensorNetworks: ITensorNetworks, siteinds, ttn +using ITensorNetworks: ITensorNetworks, TreeTensorNetwork, siteinds using ITensors.NDTensors: matrix, with_auto_fermion using ITensors: @disable_warn_order, ITensor, Index, combinedind, combiner, contract, dag, inds, removeqns @@ -53,7 +53,7 @@ end @testset "Svd approach" for root_vertex in leaf_vertices(is) # get TTN Hamiltonian directly - Hsvd = ttn(H, is; root_vertex, cutoff = 1.0e-10) + Hsvd = TreeTensorNetwork(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 @@ -63,7 +63,7 @@ end end @test Tttno ≈ Tmpo rtol = 1.0e-6 - Hsvd_lr = ttn(Hlr, is; root_vertex, cutoff = 1.0e-10) + Hsvd_lr = TreeTensorNetwork(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) @@ -102,7 +102,7 @@ end @testset "Svd approach" for root_vertex in leaf_vertices(is) # get TTN Hamiltonian directly - Hsvd = ttn(H, is; root_vertex, cutoff = 1.0e-10) + Hsvd = TreeTensorNetwork(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 @@ -113,7 +113,7 @@ end end @test Tttno ≈ Tmpo rtol = 1.0e-6 - Hsvd_lr = ttn(Hlr, is; root_vertex, cutoff = 1.0e-10) + Hsvd_lr = TreeTensorNetwork(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) @@ -142,7 +142,7 @@ end @testset "Svd approach" for root_vertex in leaf_vertices(is) # get TTN Hamiltonian directly - Hsvd = ttn(H, is; root_vertex, cutoff = 1.0e-10) + Hsvd = TreeTensorNetwork(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))] @@ -209,7 +209,7 @@ end @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) + Hsvd = TreeTensorNetwork(H, is_missing_site; root_vertex, cutoff = 1.0e-10) # get corresponding MPO Hamiltonian Hline = ITensorMPS.MPO(replace_vertices(v -> vmap[v], H), sites) @@ -220,7 +220,7 @@ end end @test Tttno ≈ Tmpo rtol = 1.0e-6 - Hsvd_lr = ttn(Hlr, is_missing_site; root_vertex, cutoff = 1.0e-10) + Hsvd_lr = TreeTensorNetwork(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) diff --git a/test/test_tebd.jl b/test/test_tebd.jl index 3e5b1564..17993cb2 100644 --- a/test/test_tebd.jl +++ b/test/test_tebd.jl @@ -27,7 +27,7 @@ ITensors.disable_warn_order() β = 2.0 Δβ = 0.2 - ψ_init = tensornetworkstate(v -> "↑", s) + ψ_init = productstate(v -> "↑", s) #E0 = expect(ℋ, ψ_init) ψ = tebd( group_terms(ℋ, g), diff --git a/test/test_ttn_expect.jl b/test/test_ttn_expect.jl index fbd74f49..2ba474cb 100644 --- a/test/test_ttn_expect.jl +++ b/test/test_ttn_expect.jl @@ -16,7 +16,7 @@ include("utils.jl") magnetization[v] = isodd(i) ? 0.5 : -0.5 end states = v -> d[v] - state = TreeTensorNetwork(tensornetworkstate(states, s)) + state = TreeTensorNetwork(productstate(states, s)) res = expect("Sz", state) @test all([isapprox(res[v], magnetization[v]; atol = 1.0e-8) for v in vertices(s)]) end diff --git a/test/test_ttn_position.jl b/test/test_ttn_position.jl index 8c9314a2..f2e0e3ab 100644 --- a/test/test_ttn_position.jl +++ b/test/test_ttn_position.jl @@ -1,7 +1,7 @@ using Dictionaries: Dictionary, Indices using Graphs: vertices using ITensorNetworks: - ITensorNetwork, ProjTTN, TreeTensorNetwork, environments, position, siteinds, ttn + ITensorNetwork, ProjTTN, TreeTensorNetwork, environments, position, siteinds using ITensors.NDTensors: with_auto_fermion using ITensors: ITensor using NamedGraphs.NamedGraphGenerators: named_comb_tree, named_path_graph @@ -25,14 +25,14 @@ using .ModelHamiltonians: ModelHamiltonians os = ModelHamiltonians.heisenberg(c) - H = ttn(os, s) + H = TreeTensorNetwork(os, s) d = Dict() for (i, v) in enumerate(vertices(s)) d[v] = isodd(i) ? "Up" : "Dn" end states = v -> d[v] - psi = TreeTensorNetwork(tensornetworkstate(states, s)) + psi = TreeTensorNetwork(productstate(states, s)) # actual test, verifies that position is out of place vs = collect(vertices(s)) diff --git a/test/test_ttno.jl b/test/test_ttno.jl index b4e8b55f..c876a3d9 100644 --- a/test/test_ttno.jl +++ b/test/test_ttno.jl @@ -1,5 +1,5 @@ using Graphs: vertices -using ITensorNetworks: contract, ortho_region, siteinds, ttn, union_all_inds +using ITensorNetworks: TreeTensorNetwork, contract, ortho_region, siteinds, union_all_inds using ITensors: @disable_warn_order, prime, random_itensor using LinearAlgebra: norm using NamedGraphs.NamedGraphGenerators: named_comb_tree @@ -25,7 +25,7 @@ using Test: @test, @testset rng = StableRNG(1234) O = random_itensor(rng, sites_o...) # dense TTN constructor from IndsNetwork - @disable_warn_order o1 = ttn(O, is_isp; cutoff) + @disable_warn_order o1 = TreeTensorNetwork(O, is_isp; cutoff) root_vertex = only(ortho_region(o1)) @disable_warn_order begin O1 = contract(o1, root_vertex) diff --git a/test/test_ttns.jl b/test/test_ttns.jl index e55be719..64fac579 100644 --- a/test/test_ttns.jl +++ b/test/test_ttns.jl @@ -1,7 +1,7 @@ using DataGraphs: vertex_data using Graphs: vertices using ITensorNetworks: - ITensorNetwork, TreeTensorNetwork, contract, ortho_region, orthogonalize, siteinds, ttn + ITensorNetwork, TreeTensorNetwork, contract, ortho_region, orthogonalize, siteinds using ITensors: @disable_warn_order, random_itensor using LinearAlgebra: norm using NamedGraphs.NamedGraphGenerators: named_comb_tree @@ -26,7 +26,7 @@ include("utils.jl") rng = StableRNG(1234) S = random_itensor(rng, vertex_data(is)...) # dense TTN constructor from IndsNetwork - @disable_warn_order s1 = ttn(S, is; cutoff) + @disable_warn_order s1 = TreeTensorNetwork(S, is; cutoff) root_vertex = only(ortho_region(s1)) @disable_warn_order begin S1 = contract(s1, root_vertex) @@ -40,7 +40,7 @@ include("utils.jl") rng = StableRNG(1234) psi = TreeTensorNetwork(random_tensornetwork(rng, sites)) # random - psi = TreeTensorNetwork(tensornetworkstate(v -> "Up", sites)) # product state + psi = TreeTensorNetwork(productstate(v -> "Up", sites)) # product state itn = ITensorNetwork(psi) # TTN → ITensorNetwork @test vertex_data(itn) == vertex_data(psi.tensornetwork) @@ -54,7 +54,7 @@ include("utils.jl") rng = StableRNG(1234) psi = TreeTensorNetwork(random_tensornetwork(rng, sites)) # random - psi = TreeTensorNetwork(tensornetworkstate(v -> "Up", sites)) # product state + psi = TreeTensorNetwork(productstate(v -> "Up", sites)) # product state v1 = collect(vertices(psi))[1] v2 = collect(vertices(psi))[2] diff --git a/test/utils.jl b/test/utils.jl index 5c0d6cc5..a9325a02 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -3,216 +3,117 @@ # `test_*.jl`). Each test file that needs these does `include("utils.jl")` # inside its gensym module. -using DataGraphs: IsUnderlyingGraph, underlying_graph, vertex_data -using Dictionaries: AbstractDictionary, Indices +using DataGraphs: underlying_graph, vertex_data +using Dictionaries: AbstractDictionary using Distributions: Distribution -using Graphs: AbstractGraph, edges, neighbors, vertices -using ITensorNetworks: ITensorNetworks, ITensorNetwork, IndsNetwork, insert_linkinds +using Graphs: AbstractGraph, edges, vertices +using ITensorNetworks: ITensorNetwork, IndsNetwork, insert_linkinds using ITensors.NDTensors: dim -using ITensors.Ops: Op -using ITensors: ITensors, ITensor, Index, contract, itensor, onehot, plev -using LinearAlgebra: normalize -using NamedGraphs.GraphsExtensions: edgetype, incident_edges, vertextype +using ITensors: ITensors, ITensor, Index, contract, itensor, onehot +using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs: NamedGraph using Random: Random, AbstractRNG -using SimpleTraits: SimpleTraits, @traitfn -# Build an `ITensorNetwork` on the graph backing the inds network `s` from a -# user-supplied per-vertex tensor builder. Each vertex `v` is given a tensor -# whose indices are `s[v]` (the site indices) followed by one fresh `Index` of -# dimension `link_space` for each edge of the graph incident to `v`. The shared -# link index is used on both endpoints so the resulting network's edges agree -# with `edges(s)`. -function _random_tensornetwork(s::IndsNetwork, build_tensor; link_space = 1) - l = Dict(e => Index(link_space, "Link") for e in edges(s)) - l = merge(l, Dict(reverse(e) => l[e] for e in edges(s))) - vd = vertex_data(s) - g = NamedGraph(underlying_graph(s)) - ts = Dict{vertextype(g), ITensor}() - for v in vertices(g) - site_inds = isassigned(vd, v) ? vd[v] : Index[] - is = (site_inds..., (l[e] for e in incident_edges(s, v))...) - ts[v] = build_tensor(is) - end - return ITensorNetwork(ts, g) +# Build an `ITensorNetwork` on `graph` by calling +# `tensor_at(v, site_inds, link_inds) -> ITensor` at each vertex. +# `site_inds_at(v) -> Vector{<:Index}` provides the per-vertex site inds. +# Link inds of bond dimension `link_space` are generated once per edge of +# `graph` and shared by both endpoints. +function _build_tensornetwork(tensor_at, graph::NamedGraph, site_inds_at; link_space) + links = Dict(e => Index(link_space, "Link") for e in edges(graph)) + tensors = Dict( + map(collect(vertices(graph))) do v + link_v = [ + haskey(links, e) ? links[e] : links[reverse(e)] + for e in incident_edges(graph, v) + ] + return v => tensor_at(v, site_inds_at(v), link_v) + end + ) + return ITensorNetwork(tensors, graph) end -# Build an ITensor network on a graph specified by the inds network `s`. -# `link_space` sets the bond dimension. Entries are drawn from a standard -# normal distribution. -function random_tensornetwork( - rng::AbstractRNG, eltype::Type, s::IndsNetwork; kwargs... - ) - return _random_tensornetwork( - s, is -> itensor(randn(rng, eltype, dim.(is)...), is...); kwargs... +# Standardize input to a `(graph::NamedGraph, site_inds_at::v -> Vector{<:Index})` pair. +function _graph_and_sites(s::IndsNetwork) + return ( + NamedGraph(underlying_graph(s)), + v -> isassigned(vertex_data(s), v) ? s[v] : Index[], ) end +_graph_and_sites(g::AbstractGraph) = (NamedGraph(g), Returns(Index[])) -function random_tensornetwork(eltype::Type, s::IndsNetwork; kwargs...) - return random_tensornetwork(Random.default_rng(), eltype, s; kwargs...) -end +# --- random_tensornetwork ---------------------------------------------------- -function random_tensornetwork(rng::AbstractRNG, s::IndsNetwork; kwargs...) - return random_tensornetwork(rng, Float64, s; kwargs...) +function random_tensornetwork( + rng::AbstractRNG, eltype::Type, sites_or_graph; link_space = 1 + ) + g, site_inds_at = _graph_and_sites(sites_or_graph) + return _build_tensornetwork(g, site_inds_at; link_space) do _, site_inds, link_inds + all_inds = [site_inds; link_inds] + return itensor(randn(rng, eltype, dim.(all_inds)...), all_inds) + end end -function random_tensornetwork(s::IndsNetwork; kwargs...) - return random_tensornetwork(Random.default_rng(), s; kwargs...) +function random_tensornetwork( + rng::AbstractRNG, distribution::Distribution, sites_or_graph; link_space = 1 + ) + g, site_inds_at = _graph_and_sites(sites_or_graph) + return _build_tensornetwork(g, site_inds_at; link_space) do _, site_inds, link_inds + all_inds = [site_inds; link_inds] + return itensor(rand(rng, distribution, dim.(all_inds)...), all_inds) + end end -# -# State / default tensornetwork builders. Replace deleted -# `ITensorNetwork(state, ::IndsNetwork)` / `ITensorNetwork(::Function, ::IndsNetwork)` -# / `ITensorNetwork(::IndsNetwork)` / `ITensorNetwork(::AbstractGraph)` ctor surface. -# - -# Convert a state spec into a callable mapping vertex -> state-spec-for-that-vertex. -to_callable(value::Type) = value -to_callable(value::Function) = value -function to_callable(value::AbstractDict) - return Base.Fix1(getindex, value) ∘ keytype(value) +# RNG / eltype / both defaults +function random_tensornetwork(rng::AbstractRNG, sites_or_graph; kwargs...) + return random_tensornetwork(rng, Float64, sites_or_graph; kwargs...) end -function to_callable(value::AbstractDictionary) - return Base.Fix1(getindex, value) ∘ keytype(value) +function random_tensornetwork(eltype::Type, sites_or_graph; kwargs...) + return random_tensornetwork(Random.default_rng(), eltype, sites_or_graph; kwargs...) end -function to_callable(value::AbstractArray{<:Any, N}) where {N} - return Base.Fix1(getindex, value) ∘ CartesianIndex +function random_tensornetwork(sites_or_graph; kwargs...) + return random_tensornetwork(Random.default_rng(), Float64, sites_or_graph; kwargs...) end -to_callable(value) = Returns(value) -# Build the actual ITensor at a vertex from a state spec and the (site, link) inds. -generic_state(f, inds::Vector) = f(inds) -generic_state(a::AbstractArray, inds::Vector) = itensor(a, inds) -function generic_state(f, inds::NamedTuple) - return generic_state(f, reduce(vcat, inds.linkinds; init = inds.siteinds)) -end -function generic_state(x::Op, inds::NamedTuple) - 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 = [ITensors.op(x.which_op, i)] - else - site_tensors = ITensor[] - 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) - site_tensors = [ITensors.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 +# --- productstate ------------------------------------------------------------- + +# Convert a state spec (string / dict / dictionary / array / function) into a +# callable mapping vertex -> state-spec-for-that-vertex. +_to_callable(value::Function) = value +_to_callable(value::AbstractDict) = Base.Fix1(getindex, value) ∘ keytype(value) +_to_callable(value::AbstractDictionary) = Base.Fix1(getindex, value) ∘ keytype(value) +_to_callable(value::AbstractArray) = Base.Fix1(getindex, value) ∘ CartesianIndex +_to_callable(value) = Returns(value) -# Lowest-level: function-callback form. `itensor_constructor(v)` returns a -# state spec for vertex `v`, which is then expanded into an actual `ITensor` -# via `generic_state`. -function tensornetworkstate(itensor_constructor::Function, s::IndsNetwork; kwargs...) +# Build a product-state ITensorNetwork: at each vertex, the on-site state +# vector (looked up via `ITensors.state(name, site_index)`) is contracted with +# `onehot` vectors on each incident link Index. Link inds are filled in by +# `insert_linkinds`, which picks the right `trivial_space` (plain `1` or +# `[QN() => 1]`) to match the site indices' QN structure. +function productstate(state, s::IndsNetwork; kwargs...) + state_at = _to_callable(state) s = insert_linkinds(s; kwargs...) g = NamedGraph(underlying_graph(s)) - ts = Dict{vertextype(g), ITensor}() - for v in vertices(g) - site_inds = get(s, v, Index[]) - edges = [edgetype(s)(v, nv) for nv in neighbors(s, v)] - link_inds = map(e -> s[e], Indices(edges)) - ts[v] = generic_state( - itensor_constructor(v), (; siteinds = site_inds, linkinds = link_inds) - ) - end - return ITensorNetwork(ts, g) -end - -# Convenience: convert a value (string, dict, function, etc.) to a per-vertex -# callable, then build. -function tensornetworkstate(value, s::IndsNetwork; kwargs...) - return tensornetworkstate(to_callable(value), s; kwargs...) + tensors = Dict( + map(collect(vertices(g))) do v + site_v = isassigned(vertex_data(s), v) ? s[v] : Index[] + link_v = reduce(vcat, (s[e] for e in incident_edges(s, v)); init = Index[]) + site_t = ITensors.state(state_at(v), only(site_v)) + return v => contract([site_t; (onehot(i => 1) for i in link_v)...]) + end + ) + return ITensorNetwork(tensors, g) end -# Eltype-converting variant: build, then convert each tensor to `elt`. -function tensornetworkstate(elt::Type, value, s::IndsNetwork; kwargs...) - tn = tensornetworkstate(to_callable(value), s; kwargs...) +function productstate(elt::Type, state, s::IndsNetwork; kwargs...) + tn = productstate(state, s; kwargs...) for v in vertices(tn) tn[v] = ITensors.convert_eltype(elt, tn[v]) end return tn end -# Graph-input forms: build the IndsNetwork first (no site inds by default), -# then forward. -@traitfn function tensornetworkstate(value::Any, g::::IsUnderlyingGraph; kwargs...) - return tensornetworkstate(value, IndsNetwork(g); kwargs...) -end -@traitfn function tensornetworkstate( - elt::Type, value::Any, g::::IsUnderlyingGraph; kwargs... - ) - return tensornetworkstate(elt, value, IndsNetwork(g); kwargs...) -end - -@traitfn function random_tensornetwork( - 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...) -end - -@traitfn function random_tensornetwork(rng::AbstractRNG, g::::IsUnderlyingGraph; kwargs...) - return random_tensornetwork(rng, Float64, g; kwargs...) -end - -@traitfn function random_tensornetwork(g::::IsUnderlyingGraph; kwargs...) - return random_tensornetwork(Random.default_rng(), g; kwargs...) -end - -# Build an ITensor network on a graph specified by the inds network `s`. -# Entries are drawn from the supplied `Distribution`. -function random_tensornetwork( - rng::AbstractRNG, distribution::Distribution, s::IndsNetwork; kwargs... - ) - return _random_tensornetwork( - s, is -> itensor(rand(rng, distribution, dim.(is)...), is...); kwargs... - ) -end - -function random_tensornetwork(distribution::Distribution, s::IndsNetwork; 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...) -end - -@traitfn function random_tensornetwork( - distribution::Distribution, g::::IsUnderlyingGraph; kwargs... - ) - return random_tensornetwork(Random.default_rng(), distribution, g; kwargs...) -end - -function random_ttn(args...; kwargs...) - return normalize( - ITensorNetworks._TreeTensorNetwork(random_tensornetwork(args...; kwargs...)) - ) -end - -function random_mps(args...; kwargs...) - return random_ttn(args...; kwargs...) -end - -function random_mps(f, is::Vector{<:Index}; kwargs...) - return random_mps(f, ITensorNetworks.path_indsnetwork(is); kwargs...) -end - -function random_mps(s::Vector{<:Index}; kwargs...) - return random_mps(ITensorNetworks.path_indsnetwork(s); kwargs...) -end +# --- ModelHamiltonians -------------------------------------------------------- # Small grab-bag of model-Hamiltonian builders used in regression tests. Kept # in a submodule so call sites remain `ModelHamiltonians.ising(g; h = ...)` etc. From e5fd3ec2efbef8195972f8174bf43574f842fa74 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 7 May 2026 21:27:04 -0400 Subject: [PATCH 3/6] Drop Distribution-typed random_tensornetwork; flatten productstate eltype path Further simplification of `test/utils.jl`: - Delete the `random_tensornetwork(rng, ::Distribution, ...)` overload (and the one-line testset that exercised it). It was the sole consumer of `Distributions` in this package, so drop the dep from `Project.toml` and `test/Project.toml`, and drop `using Distributions: Uniform` from `test_itensornetwork.jl`. - Inline the `_build_tensornetwork` and `_graph_and_sites` helpers into a two-method `random_tensornetwork(rng, eltype, ::IndsNetwork; link_space)` core, with `random_tensornetwork(rng, eltype, ::AbstractGraph; ...)` thin delegating through `IndsNetwork(g)`. The closure-passing through a `tensor_at(v, site_inds, link_inds)` callback was the only thing the helpers gave us, and it's no longer needed once the distribution variant is gone. - Make `productstate(elt::Type, state, ::IndsNetwork; ...)` the workhorse and have `productstate(state, ::IndsNetwork; ...) = productstate(Float64, state, ...)` instead of the previous direction (no-eltype builds Float64, eltype builds + post-converts). Same default behaviour, simpler chain. Co-Authored-By: Claude Opus 4.7 (1M context) --- Project.toml | 2 -- test/Project.toml | 2 -- test/test_itensornetwork.jl | 9 ----- test/utils.jl | 72 +++++++++++++------------------------ 4 files changed, 24 insertions(+), 61 deletions(-) diff --git a/Project.toml b/Project.toml index 320afd51..eac78a99 100644 --- a/Project.toml +++ b/Project.toml @@ -15,7 +15,6 @@ Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" DataGraphs = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" -Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" ITensors = "9136182c-28ba-11e9-034c-db9fb085ebd5" @@ -56,7 +55,6 @@ Compat = "3, 4" ConstructionBase = "1.6" DataGraphs = "0.4" Dictionaries = "0.4" -Distributions = "0.25.86" DocStringExtensions = "0.9" Graphs = "1.8" ITensors = "0.7, 0.8, 0.9" diff --git a/test/Project.toml b/test/Project.toml index 6370637f..9b4c1046 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -5,7 +5,6 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" DataGraphs = "b5a273c3-7e6c-41f6-98bd-8d7f1525a36a" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" -Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" GraphsFlows = "06909019-6f44-4949-96fc-b9d9aaa02889" @@ -41,7 +40,6 @@ Aqua = "0.8.11" Compat = "4.16" DataGraphs = "0.4" Dictionaries = "0.4.4" -Distributions = "0.25.118" Glob = "1.3.1" Graphs = "1.12" GraphsFlows = "0.1.1" diff --git a/test/test_itensornetwork.jl b/test/test_itensornetwork.jl index 868c9e5f..05c6dda1 100644 --- a/test/test_itensornetwork.jl +++ b/test/test_itensornetwork.jl @@ -1,5 +1,4 @@ using Dictionaries: Dictionary -using Distributions: Uniform using Graphs: degree, dijkstra_shortest_paths, edges, grid, has_vertex, ne, neighbors, nv, rem_vertex!, vertices, weights using GraphsFlows: GraphsFlows @@ -141,14 +140,6 @@ const elts = (Float32, Float64, Complex{Float32}, Complex{Float64}) @test eltype(ψ[first(vertices(ψ))]) == Float64 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) diff --git a/test/utils.jl b/test/utils.jl index a9325a02..c11469cb 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -5,7 +5,6 @@ using DataGraphs: underlying_graph, vertex_data using Dictionaries: AbstractDictionary -using Distributions: Distribution using Graphs: AbstractGraph, edges, vertices using ITensorNetworks: ITensorNetwork, IndsNetwork, insert_linkinds using ITensors.NDTensors: dim @@ -14,54 +13,34 @@ using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs: NamedGraph using Random: Random, AbstractRNG -# Build an `ITensorNetwork` on `graph` by calling -# `tensor_at(v, site_inds, link_inds) -> ITensor` at each vertex. -# `site_inds_at(v) -> Vector{<:Index}` provides the per-vertex site inds. -# Link inds of bond dimension `link_space` are generated once per edge of -# `graph` and shared by both endpoints. -function _build_tensornetwork(tensor_at, graph::NamedGraph, site_inds_at; link_space) - links = Dict(e => Index(link_space, "Link") for e in edges(graph)) +# --- random_tensornetwork ---------------------------------------------------- + +# At each vertex of `s`'s graph, place an `itensor(randn(rng, eltype, ...), inds_v)` +# whose inds are the site inds at that vertex (from `s[v]`, or empty if unassigned) +# concatenated with one fresh `Index(link_space, "Link")` per incident edge, shared +# with the other endpoint. +function random_tensornetwork( + rng::AbstractRNG, eltype::Type, s::IndsNetwork; link_space = 1 + ) + g = NamedGraph(underlying_graph(s)) + links = Dict(e => Index(link_space, "Link") for e in edges(g)) tensors = Dict( - map(collect(vertices(graph))) do v + map(collect(vertices(g))) do v + site_v = isassigned(vertex_data(s), v) ? s[v] : Index[] link_v = [ haskey(links, e) ? links[e] : links[reverse(e)] - for e in incident_edges(graph, v) + for e in incident_edges(g, v) ] - return v => tensor_at(v, site_inds_at(v), link_v) + inds_v = [site_v; link_v] + return v => itensor(randn(rng, eltype, dim.(inds_v)...), inds_v) end ) - return ITensorNetwork(tensors, graph) -end - -# Standardize input to a `(graph::NamedGraph, site_inds_at::v -> Vector{<:Index})` pair. -function _graph_and_sites(s::IndsNetwork) - return ( - NamedGraph(underlying_graph(s)), - v -> isassigned(vertex_data(s), v) ? s[v] : Index[], - ) -end -_graph_and_sites(g::AbstractGraph) = (NamedGraph(g), Returns(Index[])) - -# --- random_tensornetwork ---------------------------------------------------- - -function random_tensornetwork( - rng::AbstractRNG, eltype::Type, sites_or_graph; link_space = 1 - ) - g, site_inds_at = _graph_and_sites(sites_or_graph) - return _build_tensornetwork(g, site_inds_at; link_space) do _, site_inds, link_inds - all_inds = [site_inds; link_inds] - return itensor(randn(rng, eltype, dim.(all_inds)...), all_inds) - end + return ITensorNetwork(tensors, g) end - function random_tensornetwork( - rng::AbstractRNG, distribution::Distribution, sites_or_graph; link_space = 1 + rng::AbstractRNG, eltype::Type, g::AbstractGraph; kwargs... ) - g, site_inds_at = _graph_and_sites(sites_or_graph) - return _build_tensornetwork(g, site_inds_at; link_space) do _, site_inds, link_inds - all_inds = [site_inds; link_inds] - return itensor(rand(rng, distribution, dim.(all_inds)...), all_inds) - end + return random_tensornetwork(rng, eltype, IndsNetwork(g); kwargs...) end # RNG / eltype / both defaults @@ -90,7 +69,7 @@ _to_callable(value) = Returns(value) # `onehot` vectors on each incident link Index. Link inds are filled in by # `insert_linkinds`, which picks the right `trivial_space` (plain `1` or # `[QN() => 1]`) to match the site indices' QN structure. -function productstate(state, s::IndsNetwork; kwargs...) +function productstate(elt::Type, state, s::IndsNetwork; kwargs...) state_at = _to_callable(state) s = insert_linkinds(s; kwargs...) g = NamedGraph(underlying_graph(s)) @@ -99,18 +78,15 @@ function productstate(state, s::IndsNetwork; kwargs...) site_v = isassigned(vertex_data(s), v) ? s[v] : Index[] link_v = reduce(vcat, (s[e] for e in incident_edges(s, v)); init = Index[]) site_t = ITensors.state(state_at(v), only(site_v)) - return v => contract([site_t; (onehot(i => 1) for i in link_v)...]) + t = contract([site_t; (onehot(i => 1) for i in link_v)...]) + return v => ITensors.convert_eltype(elt, t) end ) return ITensorNetwork(tensors, g) end -function productstate(elt::Type, state, s::IndsNetwork; kwargs...) - tn = productstate(state, s; kwargs...) - for v in vertices(tn) - tn[v] = ITensors.convert_eltype(elt, tn[v]) - end - return tn +function productstate(state, s::IndsNetwork; kwargs...) + return productstate(Float64, state, s; kwargs...) end # --- ModelHamiltonians -------------------------------------------------------- From b4f1bd546ede69703e3c2c7163c3fdf53527fce3 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 7 May 2026 22:12:07 -0400 Subject: [PATCH 4/6] Add factorize_edge!/add_edge! on AbstractITensorNetwork and rewrite test productstate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define `factorize_edge!(f, tn, edge)` and `Graphs.add_edge!(tn, edge)` (graph edge + QR-threaded link) on AbstractITensorNetwork. Migrate the 7 existing internal `add_edge!(tn, ...)` call sites to `add_edge!(data_graph(tn), ...)` since they already share inds and want graph-only behavior. Rewrite `productstate` in test/utils.jl: build a site-only TN from an edgeless graph, then thread one link per edge. Use a local `_add_edge!` (onehot into both endpoints with `trivial_space(tn)`) rather than the new QR-based `add_edge!` so on-site QN flux stays on sites — the QR variant pushes flux into links and BP's default_message returns empty BlockSparse for non-trivial QN sectors. --- src/abstractitensornetwork.jl | 39 +++++++++++++++----- test/utils.jl | 68 +++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/abstractitensornetwork.jl b/src/abstractitensornetwork.jl index cbda61c1..83eacb51 100644 --- a/src/abstractitensornetwork.jl +++ b/src/abstractitensornetwork.jl @@ -4,11 +4,11 @@ using DataGraphs: DataGraphs, edge_data, get_vertex_data, is_vertex_assigned, set_vertex_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 + has_edge, ne, neighbors, rem_edge!, src, vertices using ITensors: ITensors, @Algorithm_str, ITensor, addtags, combiner, commoninds, commontags, contract, dag, inds, noprime, onehot, prime, replaceprime, replacetags, setprime, settags, sim, swaptags, tags -using LinearAlgebra: LinearAlgebra, factorize +using LinearAlgebra: LinearAlgebra, factorize, qr using MacroTools: @capture using NDTensors: NDTensors, Algorithm, dim, scalartype using NamedGraphs.GraphsExtensions: @@ -136,7 +136,7 @@ function fix_edges!(tn::AbstractITensorNetwork, v) if v ≠ vertex edge = v => vertex if !isempty(linkinds(tn, edge)) - add_edge!(tn, edge) + add_edge!(data_graph(tn), edge) end end end @@ -181,7 +181,7 @@ function Base.union(tn1::AbstractITensorNetwork, tn2::AbstractITensorNetwork; kw for v1 in vertices(tn1) for v2 in vertices(tn2) if !isempty(linkinds(tn, v1 => v2)) - add_edge!(tn, v1 => v2) + add_edge!(data_graph(tn), v1 => v2) end end end @@ -471,10 +471,10 @@ function NDTensors.contract( rem_vertex!(tn, dst(edge)) add_vertex!(tn, merged_vertex) for n_src in neighbors_src - add_edge!(tn, merged_vertex => n_src) + add_edge!(data_graph(tn), merged_vertex => n_src) end for n_dst in neighbors_dst - add_edge!(tn, merged_vertex => n_dst) + add_edge!(data_graph(tn), merged_vertex => n_dst) end @preserve_graph tn[merged_vertex] = new_itensor return tn @@ -561,11 +561,11 @@ function LinearAlgebra.factorize( add_vertex!(tn, X_vertex) add_vertex!(tn, Y_vertex) - add_edge!(tn, X_vertex => Y_vertex) + add_edge!(data_graph(tn), X_vertex => Y_vertex) for nX in neighbors_X - add_edge!(tn, X_vertex => nX) + add_edge!(data_graph(tn), X_vertex => nX) end - add_edge!(tn, Y_vertex => dst(edge)) + add_edge!(data_graph(tn), Y_vertex => dst(edge)) @preserve_graph tn[X_vertex] = X @preserve_graph tn[Y_vertex] = Y return tn @@ -855,6 +855,27 @@ function insert_linkinds( return tn end +# Factor `tn[src(edge)]` via `f` along the inds it doesn't share with +# `tn[dst(edge)]`, leaving the new factor on `src(edge)` and contracting +# the residual into `tn[dst(edge)]`. With `f = qr` and no shared inds yet, +# this threads a fresh link Index between the two endpoints. With shared +# inds present, it regauges across the existing link. +function factorize_edge!(f, tn::AbstractITensorNetwork, edge) + src_t, dst_t = tn[src(edge)], tn[dst(edge)] + left_inds = setdiff(inds(src_t), inds(dst_t)) + x, y = f(src_t, left_inds) + @preserve_graph tn[src(edge)] = x + @preserve_graph tn[dst(edge)] = y * dst_t + return tn +end + +function Graphs.add_edge!(tn::AbstractITensorNetwork, edge) + has_edge(tn, edge) && return false + add_edge!(data_graph(tn), edge) + factorize_edge!(qr, tn, edge) + return true +end + # TODO: What to output? Could be an `IndsNetwork`. Or maybe # that would be a different function `commonindsnetwork`. # Even in that case, this could output a `Dictionary` diff --git a/test/utils.jl b/test/utils.jl index c11469cb..e612f78c 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -4,11 +4,11 @@ # inside its gensym module. using DataGraphs: underlying_graph, vertex_data -using Dictionaries: AbstractDictionary -using Graphs: AbstractGraph, edges, vertices -using ITensorNetworks: ITensorNetwork, IndsNetwork, insert_linkinds +using Graphs: AbstractGraph, dst, edges, src, vertices +using ITensorNetworks.ITensorsExtensions: trivial_space +using ITensorNetworks: ITensorNetwork, IndsNetwork using ITensors.NDTensors: dim -using ITensors: ITensors, ITensor, Index, contract, itensor, onehot +using ITensors: ITensors, ITensor, Index, dag, itensor, onehot using NamedGraphs.GraphsExtensions: incident_edges using NamedGraphs: NamedGraph using Random: Random, AbstractRNG @@ -56,38 +56,46 @@ end # --- productstate ------------------------------------------------------------- -# Convert a state spec (string / dict / dictionary / array / function) into a -# callable mapping vertex -> state-spec-for-that-vertex. -_to_callable(value::Function) = value -_to_callable(value::AbstractDict) = Base.Fix1(getindex, value) ∘ keytype(value) -_to_callable(value::AbstractDictionary) = Base.Fix1(getindex, value) ∘ keytype(value) -_to_callable(value::AbstractArray) = Base.Fix1(getindex, value) ∘ CartesianIndex -_to_callable(value) = Returns(value) - -# Build a product-state ITensorNetwork: at each vertex, the on-site state -# vector (looked up via `ITensors.state(name, site_index)`) is contracted with -# `onehot` vectors on each incident link Index. Link inds are filled in by -# `insert_linkinds`, which picks the right `trivial_space` (plain `1` or -# `[QN() => 1]`) to match the site indices' QN structure. -function productstate(elt::Type, state, s::IndsNetwork; kwargs...) - state_at = _to_callable(state) - s = insert_linkinds(s; kwargs...) - g = NamedGraph(underlying_graph(s)) +# Thread a fresh trivial-QN dim-1 link Index between `src(edge)` and `dst(edge)` +# by outer-producting `onehot` basis vectors into each endpoint tensor. This is +# the productstate-specific analog of `ITensorNetworks.add_edge!` — the latter +# uses QR, which would push site-state QN flux into the link and leave BP's +# `default_message` (single-index `delta`) with no compatible block. `onehot` +# defaults to `Float64`, so we typecast it to `elt` to keep `productstate(elt, +# ...)` element-type-preserving. +function _add_edge!(elt::Type, tn, edge) + iₑ = Index(trivial_space(tn), "Link") + X = ITensors.convert_eltype(elt, onehot(iₑ => 1)) + tn[src(edge)] *= X + tn[dst(edge)] *= dag(X) + return tn +end + +# Build a product-state ITensorNetwork: start from a site-only TN (one tensor +# per vertex with just the on-site state vector, looked up via +# `ITensors.state(name, site_index)`), then add each edge from the original +# IndsNetwork via `_add_edge!`. `state` is anything indexable by vertex (dict, +# dictionary, array, ...); the `Function` method just materializes a Dict first. +function productstate(elt::Type, state::Function, s::IndsNetwork) + return productstate(elt, Dict(v => state(v) for v in vertices(s)), s) +end +function productstate(elt::Type, state, s::IndsNetwork) + g_full = NamedGraph(underlying_graph(s)) + g_empty = NamedGraph(collect(vertices(g_full))) tensors = Dict( - map(collect(vertices(g))) do v + map(collect(vertices(g_empty))) do v site_v = isassigned(vertex_data(s), v) ? s[v] : Index[] - link_v = reduce(vcat, (s[e] for e in incident_edges(s, v)); init = Index[]) - site_t = ITensors.state(state_at(v), only(site_v)) - t = contract([site_t; (onehot(i => 1) for i in link_v)...]) + t = ITensors.state(state[v], only(site_v)) return v => ITensors.convert_eltype(elt, t) end ) - return ITensorNetwork(tensors, g) -end - -function productstate(state, s::IndsNetwork; kwargs...) - return productstate(Float64, state, s; kwargs...) + tn = ITensorNetwork(tensors, g_empty) + for e in edges(g_full) + _add_edge!(elt, tn, e) + end + return tn end +productstate(state, s::IndsNetwork) = productstate(Float64, state, s) # --- ModelHamiltonians -------------------------------------------------------- From a93b2d285a0c52c56379c3dd5d3caaacdafe3a53 Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Thu, 7 May 2026 22:27:06 -0400 Subject: [PATCH 5/6] test/utils.jl: refactor random_tensornetwork core signature, simplify productstate random_tensornetwork's core method now takes (rng, eltype, graph::AbstractGraph, siteinds; link_space). The IndsNetwork variant extracts siteinds via a per-vertex dict; the plain-graph variant supplies empty siteinds. Symmetrize the link dictionary with merge(l, Dict(reverse(e) => l[e])) so incident_edges lookup doesn't need a reverse-key fallback. Rename `sites_or_graph` to `sites` in the default-RNG/eltype wrappers. productstate drops the g_full/g_empty pair and iterates s's vertices and edges directly. --- test/utils.jl | 63 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/test/utils.jl b/test/utils.jl index e612f78c..ffcec865 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -15,43 +15,59 @@ using Random: Random, AbstractRNG # --- random_tensornetwork ---------------------------------------------------- -# At each vertex of `s`'s graph, place an `itensor(randn(rng, eltype, ...), inds_v)` -# whose inds are the site inds at that vertex (from `s[v]`, or empty if unassigned) -# concatenated with one fresh `Index(link_space, "Link")` per incident edge, shared -# with the other endpoint. +# Core: at each vertex of `graph`, place an `itensor(randn(rng, eltype, ...), inds_v)` +# whose inds are `siteinds[v]` concatenated with one fresh `Index(link_space, "Link")` +# per incident edge, shared with the other endpoint. `siteinds` is anything indexable +# by vertex (`keys(siteinds)` matches `vertices(graph)`); use `Index[]` per vertex for +# no site inds. function random_tensornetwork( - rng::AbstractRNG, eltype::Type, s::IndsNetwork; link_space = 1 + rng::AbstractRNG, eltype::Type, graph::AbstractGraph, siteinds; link_space = 1 ) - g = NamedGraph(underlying_graph(s)) + g = NamedGraph(graph) links = Dict(e => Index(link_space, "Link") for e in edges(g)) + links = merge(links, Dict(reverse(e) => links[e] for e in edges(g))) tensors = Dict( map(collect(vertices(g))) do v - site_v = isassigned(vertex_data(s), v) ? s[v] : Index[] - link_v = [ - haskey(links, e) ? links[e] : links[reverse(e)] - for e in incident_edges(g, v) - ] - inds_v = [site_v; link_v] + link_v = [links[e] for e in incident_edges(g, v)] + inds_v = [siteinds[v]; link_v] return v => itensor(randn(rng, eltype, dim.(inds_v)...), inds_v) end ) return ITensorNetwork(tensors, g) end + +# `IndsNetwork`: extract site inds (`Index[]` where unassigned). +function random_tensornetwork( + rng::AbstractRNG, eltype::Type, s::IndsNetwork; kwargs... + ) + siteinds = Dict( + v => isassigned(vertex_data(s), v) ? s[v] : Index[] for v in vertices(s) + ) + return random_tensornetwork(rng, eltype, underlying_graph(s), siteinds; kwargs...) +end + +# Plain graph: no site inds. function random_tensornetwork( rng::AbstractRNG, eltype::Type, g::AbstractGraph; kwargs... ) - return random_tensornetwork(rng, eltype, IndsNetwork(g); kwargs...) + return random_tensornetwork( + rng, + eltype, + g, + Dict(v => Index[] for v in vertices(g)); + kwargs... + ) end # RNG / eltype / both defaults -function random_tensornetwork(rng::AbstractRNG, sites_or_graph; kwargs...) - return random_tensornetwork(rng, Float64, sites_or_graph; kwargs...) +function random_tensornetwork(rng::AbstractRNG, sites; kwargs...) + return random_tensornetwork(rng, Float64, sites; kwargs...) end -function random_tensornetwork(eltype::Type, sites_or_graph; kwargs...) - return random_tensornetwork(Random.default_rng(), eltype, sites_or_graph; kwargs...) +function random_tensornetwork(eltype::Type, sites; kwargs...) + return random_tensornetwork(Random.default_rng(), eltype, sites; kwargs...) end -function random_tensornetwork(sites_or_graph; kwargs...) - return random_tensornetwork(Random.default_rng(), Float64, sites_or_graph; kwargs...) +function random_tensornetwork(sites; kwargs...) + return random_tensornetwork(Random.default_rng(), Float64, sites; kwargs...) end # --- productstate ------------------------------------------------------------- @@ -80,17 +96,16 @@ function productstate(elt::Type, state::Function, s::IndsNetwork) return productstate(elt, Dict(v => state(v) for v in vertices(s)), s) end function productstate(elt::Type, state, s::IndsNetwork) - g_full = NamedGraph(underlying_graph(s)) - g_empty = NamedGraph(collect(vertices(g_full))) + g = NamedGraph(collect(vertices(s))) tensors = Dict( - map(collect(vertices(g_empty))) do v + map(collect(vertices(s))) do v site_v = isassigned(vertex_data(s), v) ? s[v] : Index[] t = ITensors.state(state[v], only(site_v)) return v => ITensors.convert_eltype(elt, t) end ) - tn = ITensorNetwork(tensors, g_empty) - for e in edges(g_full) + tn = ITensorNetwork(tensors, g) + for e in edges(s) _add_edge!(elt, tn, e) end return tn From 2b1237a0f717d3427717829c24fc8a9c544a0bec Mon Sep 17 00:00:00 2001 From: Matthew Fishman Date: Fri, 8 May 2026 20:15:32 -0400 Subject: [PATCH 6/6] Switch to dev version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index eac78a99..531e8ee3 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorNetworks" uuid = "2919e153-833c-4bdc-8836-1ea460a35fc7" -version = "0.22.0" +version = "0.22.0-DEV" authors = ["Matthew Fishman , Joseph Tindall and contributors"] [workspace]